一、 复制信息记录表概述
1. 复制信息表简介
复制信息表用于在从库在复制主库数据期间,保存从主库转发到从库的二进制日志事件、中继日志当前状态和位置的信息。
-
master.info文件或者mysql.slave_master_info表:用于保存从库IO线程连接主库的连接状态、帐号、IP、端口、密码以及IO线程当前读取主库binlog的file和position等信息,被称为IO线程信息日志。默认保存在master.info文件中,如果需要保存在mysql.slave_master_info表,需要在启动前设置master-info-repository = TABLE。
-
relay-log.info文件或者mysql.slave_relay_log_info表:用于记录最新的relay log的file和position以及SQL线程当前重放的事件对应主库binlog的file和position,被称为SQL线程信息日志。默认保存在relay-log.info文件中,如果需要保存在mysql.slave_relay_log_info表,需要在启动前设置relay-log-info-repository = TABLE。
- 设置relay_log_info_repository和master_info_repository为TABLE可以提高数据库本身或者所在主机意外终止之后crash recovery的能力,且可以保证数据一致性。
-
从库crash时,SQL线程可能还有一部分relay log重放延迟,另外,IO线程的位置也可能正处于一个事务的中间,并不完整,所以必须在从库上启用参数relay-log-recovery=ON,启用该参数之后,从库crash recovery时会清理掉SQL线程未重放完成的relay log,并以SQL线程的位置为准重置掉IO线程的位置重新从主库请求。
- 如果这两张表在MySQL启动时无法被mysqld初始化,mysqld可以继续启动但会在错误日志中写入警告信息,这种情况常见于MySQL从不支持该表的版本升级到支持该表的版本。
-
不要尝试手动更新slave_master_info或slave_relay_log_info表,否则后果自负。
-
从库中复制线程在工作时,只允许对这两张表做查询不允许修改。
2. 什么是中继日志(relay log)
中继日志与二进制日志保存的event数据类似(中继日志中包含更多信息),也是由一组包含描述数据库变更的事件数据的文件组成,这些文件名后缀带连续编号,此外,还有一个包含所有正在使用的中继日志文件名称的索引文件。
中继日志中的数据存放格式与二进制日志相同,都可以使用mysqlbinlog命令来提取数据。中继日志默认保存在datadir下,文件名格式为:host_name-relay-bin.nnnnnn,连续的中继日志文件从000001开始的连续序列号创建。使用索引文件来跟踪当前正在使用的中继日志文件,索引文件默认保存在datadir下,文件名格式为:host_name-relay-bin.index。
中继日志文件和中继日志索引文件名称可分别使用--relay-log和--relay-log-index参数选项指定值覆盖默认值,如果文件名使用默认值,则要注意主机名称不能修改,否则会报无法打开中继日志的错误,建议使用参数选项指定固定的文件名称前缀。如果已经出现了这种情况了,需要修改index文件和datadir下的中继日志文件名前缀为新的主机名,然后重启从库。
3. 何时会产生新的中继日志文件
-
I/O线程启动时
-
执行FLUSH LOGS 或 mysqladmin flush-logs 命令时
-
当前中继日志文件变得“太大”时,日志滚动规则如下:
* 如果max_relay_log_size>0,按照此参数指定的大小进行滚动。
* 如果max_relay_log_size=0,按照max_binlog_size指定的大小进行滚动。
SQL线程在执行完relay log之后,会自行决定何时清理掉这些已经执行完成的relay log文件,但如果使用FLUSH LOGS语句或mysqladmin flush-logs命令强制滚动中继日志时,SQL线程可能会同时清理掉已经执行完成的relay log文件。
二、 复制信息记录表详解
1. slave_master_info
该表提供查询IO线程读取主库的位置信息、从库连接主库的IP、账号、端口、密码等信息。
select * from slave_master_info\G;
*************************** 1. row ***************************
Number_of_lines: 25
Master_log_name: mysql-bin.000292
Master_log_pos: 194
Host: 192.168.2.148
User_name: qfsys
User_password: letsg0
Port: 3306
Connect_retry: 60
Enabled_ssl: 0
Ssl_ca:
Ssl_capath:
Ssl_cert:
Ssl_cipher:
Ssl_key:
Ssl_verify_server_cert: 0
Heartbeat: 5
Bind:
Ignored_server_ids: 0
Uuid: ec123678-5e26-11e7-9d38-000c295e08a0
Retry_count: 86400
Ssl_crl:
Ssl_crlpath:
Enabled_auto_position: 0
Channel_name:
Tls_version:
1 row in set (0.00 sec)
表字段与show slave status输出、master.info文件中的行信息对应关系如下:
master.info中行数 | slave_master_info表字段 | show slave status输出字段 | 字段含义描述 |
1 | Number_of_lines | [None] | 表示master.info中的信息行数或者slave_master_info表中的信息字段数 |
2 | Master_log_name | Master_Log_File | 表示从库IO线程当前读取主库最新的binlog file名称 |
3 | Master_log_pos | Read_Master_Log_Pos | 表示从库IO线程当前读取主库最新的binlog position |
4 | Host | Master_Host | 表示从库IO线程当前正连接的主库IO或者主机名 |
5 | User_name | Master_User | 表示从库IO线程用于连接主库用户名 |
6 | User_password | [None] | 表示从库IO线程用于连接主库的用户密码 |
7 | Port | Master_Port | 表示从库IO线程所连接主库的网络端口 |
8 | Connect_retry | Connect_Retry | 表示从库IO线程断线重连主库的间隔时间,单位为秒,默认值为60 |
9 | Enabled_ssl | Master_SSL_Allowed | 表示主从之间的连接是否支持SSL |
10 | Ssl_ca | Master_SSL_CA_File | 表示CA(Certificate Authority )认证文件名 |
11 | Ssl_capath | Master_SSL_CA_Path | 表示CA(Certificate Authority )认证文件路径 |
12 | Ssl_cert | Master_SSL_Cert | 表示SSL认证证书文件名 |
13 | Ssl_cipher | Master_SSL_Cipher | 表示用于SSL连接握手中可能使用到的密码列表 |
14 | Ssl_key | Master_SSL_Key | 表示SSL认证的密钥文件名 |
15 | Ssl_verify_server_cert | Master_SSL_Verify_Server_Cert | 表示是否需要校验server的证书 |
16 | Heartbeat | [None] | 表示主从之间的复制心跳包的间隔时间,单位为秒 |
17 | Bind | Master_Bind | 表示从库可用于连接主库的网络接口,默认为空 |
18 | Ignored_server_ids | Replicate_Ignore_Server_Ids | 表示从库复制需要忽略哪些server-id,注意:这是一个列表,第一个数字表示需要忽略的实例server-id总数 |
19 | Uuid | Master_UUID | 表示主库的UUID |
20 | Retry_count | Master_Retry_Count | 表示从库最大允许重连主库的次数 |
21 | Ssl_crl | [None] | SSL证书撤销列表文件的路径 |
22 | Ssl_crl_path | [None] | 包含ssl证书吊销列表文件的目录路径 |
23 | Enabled_auto_position | Auto_position | 表示从库是否启用在主库中自动寻找位置的功能(使用1时启动自动寻找位置,如果使用auto_position=0,则不会自耦东找位置) |
24 | Channel_name | Channel_name | 表示从库复制通道名称,一个通道代表一个复制源 |
25 | Tls_Version | Master_TLS_Version | 表示在Master上的TLS版本号 |
2. slave_relay_log_info
该表提供查询SQL线程重放的二进制文件对应的主库位置和relay log当前最新的位置。
select * from slave_relay_log_info\G;
*************************** 1. row ***************************
Number_of_lines: 7
Relay_log_name: /home/mysql/data/mysqldata1/relaylog/mysql-relay-bin.000205
Relay_log_pos: 14097976
Master_log_name: mysql-bin.000060
Master_log_pos: 21996812
Sql_delay: 0
Number_of_workers: 16
Id: 1
Channel_name:
1 row in set (0.00 sec)
表字段与show slave status输出字段、relay-log.info文件中的行信息对应关系及其表字段含义如下:
relay-log.info文件中的行数 | mysql.slave_relay_log_info表字段 | show slave status命令输出字段 | 字段含义描述 |
---|---|---|---|
1 | Number_of_lines | [None] | 表示relay-log.info中的信息行数或者slave_relay_log_info表中的信息字段数,用于版本化表定义 |
2 | Relay_log_name | Relay_Log_File | 表示当前最新的relay log文件名称 |
3 | Relay_log_pos | Relay_Log_Pos | 表示当前最新的relay log文件对应的最近一次完整接收的event的位置 |
4 | Master_log_name | Relay_Master_Log_File | 表示SQL线程当前正在重放的中继日志对应的主库binlog 文件名 |
5 | Master_log_pos | Exec_Master_Log_Pos | 表示SQL线程当前正在重放的中继日志对应主库binlog 文件中的位置 |
6 | Sql_delay | SQL_Delay | 表示延迟复制指定的从库必须延迟主库多少秒 |
7 | Number_of_workers | [None] | 表示从库当前并行复制有多少个worker线程 |
8 | Id | [None] | 用于内部唯一标记表中的每一行记录,目前总是1 |
9 | Channel_name | Channel_name | 表示从库复制通道名称,用于多源复制,一个通道对应一个主库源 |
3. slave_worker_info
该表提供查询多线程复制时的worker线程状态信息,与performance_schema.replication_applier_status_by_worker表的区别是: 前者记录worker线程重放的relay log和主库binlog位置信息,而后者记录的是worker线程重放的GTID位置信息。
select * from slave_worker_info limit 1\G;
*************************** 1. row ***************************
Id: 1
Relay_log_name:
Relay_log_pos: 0
Master_log_name:
Master_log_pos: 0
Checkpoint_relay_log_name:
Checkpoint_relay_log_pos: 0
Checkpoint_master_log_name:
Checkpoint_master_log_pos: 0
Checkpoint_seqno: 0
Checkpoint_group_size: 64
Checkpoint_group_bitmap:
Channel_name:
1 row in set (0.00 sec)
表字段含义:
-
Id:表中数据的ID(worker线程的ID)对应performance_schema.replication_applier_status_by_worker表的WORKER_ID字段。如果复制停止,该字段值仍然存在,而replication_applier_status_by_worker表中THREAD_ID字段值会清空。
-
Relay_log_name:每个worker线程当前最新执行到的relay log文件名。
-
Relay_log_pos:每个worker线程当前最新执行到的relay log文件中的position。
-
Master_log_name:每个worker线程当前最新执行到的主库binary log文件名。
-
Master_log_pos:每个worker线程当前最新执行到的主库binary log文件中的position。
-
Checkpoint_relay_log_name:每个worker线程最新检查点的relay log文件名。
-
Checkpoint_relay_log_pos:每个worker线程最新检查点的relay log文件中的position。
-
Checkpoint_master_log_name:每个worker线程最新检查点对应主库的binary log文件名。
-
Checkpoint_master_log_pos:每个worker线程最新检查点对应主库的binary log文件中的position。
-
Checkpoint_seqno:每个worker线程当前最新执行完成的事务号,这个事务号的大小值是相对于每个worker线程自己的最新检查点而言的,并不是真正的事务号。
-
Checkpoint_group_size:表示每个worker线程的执行队列大于这个字段值时,就会触发当前worker线程执行一次检查点。
-
Checkpoint_group_bitmap:用于从库crash之后recovery的关键值,它是一个位图值,表示每个worker线程在自己的最新检查点中已经执行的事务。
-
Channel_name: 复制通道名称,多主复制时,显示指定的复制通道名称,单主复制时该字段为空。
该表中记录的内容对从库多线程复制crash recovery至关重要,所以下文对该表中记录的内容如何作用于crash recovery过程进行一些必要的说明。
从库多线程复制如何做复制分发?
-
5.7中加入了基于事务的并行复制,主库在binlog的GTID事件中新加入了last_commit和sequence_number标记,用于表示在每个binlog中的每个group中的提交顺序。在每个给定的binlog中,每个group中的last_commit总是为上一个group中最大的sequence_number、为当前group中最小的sequence_number - 1。在每个binlog中,last_commit总是从0开始计数,sequence_number总是从1开始计数。
-
从库relay log中记录的主库binlog,不会改变主库的server id、时间戳信息以及last_commit和sequence_number值,这样,从库SQL线程在执行binlog重放时,就可以依据这些信息决定从库是否需要严格按照主库提交顺序进行提交(从库重放的事务只是分发顺序按照主库提交顺序,但是从库自己在提交这些事务时是否按照主库提交顺序进行提交,还需要看从库自己的slave_preserve_commit_order变量设置,设置为1则严格按照relay log中的顺序进行提交,设置为0从库会自行决定提交顺序)。
-
SQL线程并行分发原理。
* SQL协调器线程读取到一个新的事务,取出last_commit和sequence_number值。
* SQL协调器线程判断取出的新事务的当前last_commit是否大于当前已执行完成的sequence_number中的最小值(Low water mark,简称LWM,也叫低水位线标记)。
* 如果SQL协调器线程读取到的当前事务的last_commit大于当前已执行完成的sequence_number值,则说明上一个group中的事务还没有全部执行完成,此时SQL协调器线程需要等待所有的worker线程执行完成上一个group中的事务,等待LWM变大,直到当前读取到的事务的last_commit与当前已执行完成的事务的最小sequence_number值相等才可以继续分发新的事务给空闲的worker线程(并行复制是针对每个group内的事务才可以并行复制,所以,group之间是串行的,一个group未执行完成之前,下一个group的事务是需要进行等待的。 只有同一个group内的事务之间才可以并行执行。根据上文中的描述,每个group中的事务的last_commit总是为当前group中最小的sequence_number - 1,即,如果SQL协调器线程读取到的当前事务的last_commit小于当前已执行完成事务的最小的sequence_number 就说明当前所有worker线程正在执行的事务处于同一个group中,那么也就是说SQL协调器线程可以继续往下寻找空闲的worker线程进行分发,否则SQL协调器线程就需要进行等待)。
* SQL协调器线程通过统计worker线程返回的状态信息,寻找一个空闲的worker线程,如果没有空闲的线程,则SQL协调器线程需要进行等待,知道找到一个空闲的worker线程为止(如果有多个worker线程,则SQL协调器线程随机选择一个空闲的worker线程进行分发)。
* 将当前读取到的事务的binlog event分发给选定的空闲worker线程,之后worker线程会去应用这个事务,然后SQL协调器线程继续读取新的binlog event(注意,SQL协调器线程分发是按照event为单位的,不是事务单位,所以,如果当一个事务的第一个event分发给了给定worker线程之后,后续读取到的新的event如果同属于一个事务,则进入下一个事务之前的所有event都会分发给同一个worker线程处理。 当一个事务中所有的binlog event组分发完成,读取到下一个新的事务时,SQL协调器线程会重复以上判断流程)。
从库多线程复制的crash recovery
-
从前面多线程复制分发的原理我们可以知道,处于同一个group中的事务是并行应用的,且事务是随机分配的,在从库正常运行过程当中,如果任意掐一刻下去,那么所有worker线程正在执行的事务中,哪些是已经执行完成的,哪些还未执行完成其实是无法使用单个位置来确定(因为从库并行复制时有可能是乱序提交: 需要看slave_preserve_commit_order变量如何设置),也就是说所有worker线程中正在执行的最大位置和最小位置之间可能有断点。那MySQL是如何解决从库crash recovery的断点续做问题的呢?
-
MySQL 为了解决这个问题,对worker线程的执行状态做了很多记录工作,首先,维护了一个队列,这个队列叫做GAQ(Group Assigned Queue),当SQL协调器线程在分配某一个事务时,首先会将这个事务加入到这个队列,然后,才会去按照规则来寻找一个空闲的worker线程来执行,如下图(郑重声明: 该图来自书籍《MySQL 运维内参》):
每一个事务在分发到worker线程之后,都会分配一个编号,这个编号在某一段时间内,都是相对固定的,这个编号一旦被分配,就不会再改变。 在事务被某个worker线程执行完成之后,它的位置信息就会被flush一次,这与5.5版本中的relay_log_info记录的原理是类似的(relay_log_info中存放了从库当前SQL线程重放的位置),但是现在是多线程,每个worker线程的执行位置不能直接存放在relay_log_info中了,relay_log_info中存放的是所有worker线程汇总之后的位置,每个worker线程独立的位置信息存放在了mysql.slave_worker_info表中,在该表中,有多少个并行复制线程,就有多少行记录(如果是多主复制,则每个复制通道都有slave_parallel_workers变量指定的记录数)。
mysql.slave_worker_info表中,Checkpoint开头的字段记录了每个worker线程的检查点相关的信息(这里与innodb存储引擎的检查点不同,但是概念相通),worker线程的检查点的作用是什么呢?
-
前面说了SQL协调器线程在分配事务给worker线程之前会将事务先存放到GAQ队列中,但是这个队列的长度是有限的(是不是很熟悉? 跟redo log的总大小是有限的概念类似),不可能无限制的增长下去,所以必须要在这个队列中,找到一个位置点,这个位置点就是GAQ的起点位置,这个位置点之前的binlog就表示已经执行完成了。确定这个位置的过程,就叫做检查点。在多线程复制的执行过程中,随着每个worker线程不断第应用事务的binlog,检查点在GAQ中被不断地向前推进,每个worker线程通过Checkpoint_point_bitmap字段记录自己已经执行过的事务和每个已执行事务与之对应的当时的最新检查点的相对位置,这样一来,当复制意外终端之后,重新开始复制时,就可以通过所有的worker线程记录的Checkpoint_point_bitmap字段来计算出哪些事务是已经执行过的,哪些事务是还未执行的,即通过所有worker线程记录的Checkpoint_point_bitmap信息执行一次检查点操作就可以找到一个合适的恢复位置,执行检查点的大概过程如下(注意:这里是执行检查点的过程,与从库crash recovery过程无关):
* 在GAQ队列中,从尾部开始扫描,如果是已经执行过的事务,则直接将其从队列中删除。
* 持续扫描GAQ队列,直到找到一个未执行过的事务为止即停止扫描。
* 上述步骤中扫描动作停止前扫描到的最后一个事务被确定为检查点的最新位置,并且别标记为LWM(低水位线标记)。
* 将当前LWM这个事务对应的位置(master_log_pos和relay_log_pos位置)设置为此次检查点对应的位置。
* 通过所有的worker线程检查自己的检查点,也就是查看每个worker线程自己的Checkpoint_seqno字段值,这个字段值是每个worker线程在执行事务提交时更新的,更新的字段值为每个worker线程在做事务提交时对应的最新检查点的相对位置。
* 将本次执行检查点的位置记录到mysql.slave_relay_log_info表中,作为全局bin log应用的位置。
-
现在,我们来看从库crash recovery的过程:
* 首先,读取mysql.slave_master_info、mysql.slave_relay_log_info、mysql.slave_worker_info表中的信息读取出来,从mysql.slave_master_info表中找到连接主库的信息,从mysql.slave_relay_log_info表中找到全局最新的复制位置以及worker线程个数,从mysql.slave_worker_info表中找到每一个worker线程对应的复制信息位置。
* 然后,根据mysql.slave_relay_log_info表中的位置(这个位置就是全局最新的检查点位置)为准来判断所有worker线程的位置,在这个位置之前的worker线程位置就表示已经执行过的了,直接剔除,在这个位置之后的worker线程位置就表示这些事务是还没有执行过的(根据每个worker线程在mysql.slave_worker_info表中记录的Checkpoint_seqno和Checkpoint_group_bitmap字段计算出自己哪些事务没有执行过,然后通过每个worker线程在mysql.slave_worker_info表中记录的其他checkpoint字段信息转换为对应的全局检查点的位置。 然后根据所有worker线程的转换位置信息汇总为一个共同的bitmap,根据这个共同的bitmap来比对mysql.slave_relay_log_info表中的位置就可以提取出哪些事务还没有执行过),找出了哪些事务还没有执行之后,把这些事务串行地一个一个地去重新应用(应用一个更新一次mysql.slave_relay_log_info表,为什么要串行,这是为了在恢复过程中如果再次跪了,还可以正确地恢复位置),应用完成之后清空mysql.slave_worker_info表。然后启动复制线程,继续从主库拉取最新的binlog进行数据复制。
PS: 如果在主从复制架构中,有2个以上的从库,且从库永远不做提升主库的操作时,可以使用如下方法优化从库延迟(在该场景下,从库无需担心数据丢失问题,因为有另外一个从库兜底+不做主从切换,只需要专心提供快速应用主库binlog与只读业务即可)。
-
关闭log_slave_updates参数,减少从库binlog写入量(如果不做级联复制甚至可以同时关闭binlog)。
-
设置innodb_flush_log_at_trx_commit为0或者2,减少事务提交时redo log的等待频率。
-
设置sync_binlog为默认值或者更大的值,减少事务提交时binlog的等待频率。
-
设置slave_preserve_commit_order参数为OFF(默认为OFF,设置为ON时要求开启binlog和log_slave_updates参数),减少事务严格按照主库顺序提交时的提交等待时间。
5. gtid_executed
前面介绍的三张表中都不包括GTID信息,gtid_executed表才是GTID信息的持久表,该表用于存储所有事务分配的 GTID集合,GTID集合由UUID集合构成,每个UUID集合的组成为:uuid:interval[:interval]...,例如 :28b13b49-3dfb-11e8-a76d-5254002a54f2:1-600401, 3ff62ef2-3dfb-11e8-a448-525400c33752:1-110133
-
GTID是在整个复制拓扑中是全局唯一的,GTID中的事务号是一个单调递增的无间隙数字。 正常情况下,客户端的数据修改在执行commit时会分配一个GTID,且会记录到binlog中,这些GTID通过复制组件在其他实例中进行重放时也会保留GTID来源不变。但是如果客户端自行使用sql_log_bin变量关闭了binlog记录或者客户端执行的是一个只读事务,那么server不会分配GTID,在binlog中也不会有GTID记录。
-
当某个从库接受到自己的GTID集合中已经包含的GTID时,会忽略这个已存在的GTID,并且不会报错,事务也不会被执行。
从MySQL 5.7.5开始,GTID存储在mysql数据库的名为gtid_executed的表中。 对于每个GTID集合,默认情况下值记录每个GTID集合的起始和结束的事务号对应的GTID,该表只在数据库初始化或者执行update_grade升级的时候创建,不允许手工创建于修改。当实例本身有客户端访问数据写入或者有从其他主库通过复制插件同步数据的时候,该表中会有新的GTID记录写入,另外,该表中的记录还会在binlog滚动或者实例重启的时候被更新(日志滚动时该表需要把除了最新的binlog之外其他binlog中的所有GTID结合记录到该表中,实例重启时,需要把所有的binlog中的GTID集合记录到该表中)。
由于有mysql.gtid_executed表记录GTID(避免了binlog丢失的时候丢失GTID历史记录),所以,从5.7.5版本开始,在复制拓扑中的从库允许关闭binlog,也允许在binlog开启的情况下关闭log_slave_updates变量。
由于GTID必须要再gtid_mode为ON或者为ON_PERMISSIVE时才会生成,所以自然该表中的记录也需要依赖于gtid_mode变量为ON或ON_PERMISSIVE时才会进行记录,另外,该表中是否实时存储GTID,取决于binlog日志是否开启,或者binlog启用时是否启用log_slave_updates变量,如下:
-
当禁用二进制日志记录(log_bin为OFF),或者启用binlog但禁用log_slave_updates,则Server会在每个事物提交时把属于该事物的GTID同时更新到该表中。 此时,该表的GTID周期性自动压缩功能激活,每达到gtid_executed_compression_period系统变量指定的事物数量压缩一次该表中的GTID集合(也就是把每个UUID对应的事务号的记录取一个最大值,取一个最小值,删除中间值),要注意:周期性自动压缩功能仅针对从库,对主库无效,因为主库必须启用binlog,且log_slave_updates参数不影响主库。
-
如果启用二进制日志记录(log_bin为ON)且log_slave_updates参数也启用,则周期性自动压缩功能失效,该表中的记录只会在binlog日志滚动或者服务器关闭时才会进行压缩,且会把除了最后一个binlog之外,其他所有binlog中包含的GTID集合写入该表中。
-
注意:
* 如果启用二进制日志记录(log_bin为ON)且log_slave_updates参数也启用,那么该表不会实时记录GTID,也就是说,完整的GTID集合,有一部分记录在该表中,有一部分是记录在binlog中的,如果一旦server发生crash,那么在crash recovery时会读取binlog中最新的GTID集合并合并到该表中。
* 该表中的记录在执行reset master语句时会被清空。
# 假设表中有如下实时记录的GTID记录
mysql> SELECT * FROM mysql.gtid_executed;
+ -------------------------------------- + ---------- ------ + -------------- +
| source_uuid | interval_start | interval_end |
| -------------------------------------- + ---------- ------ + -------------- |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 37 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38 | 38 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39 | 39 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 40 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41 | 41 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42 | 42 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43 | 43 |
...
# 那么,每达到gtid_executed_compression_period变量定义的事务个数时,激活压缩功能,GTID被压缩为一行记录,如下
+ -------------------------------------- + ---------- ------ + -------------- +
| source_uuid | interval_start | interval_end |
| -------------------------------------- + ---------- ------ + -------------- |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 43 |
...
# 注意:当gtid_executed_compression_period系统变量设置为0时,周期性自动压缩功能失效,你需要预防该表被撑爆的风险
表字段含义。
-
source_uuid: 代表数据来源的GTID集合。
-
interval_start: 每个UUID集合的最小事务号。
-
interval_end: 每个UUID集合的最大事务号。
对该表的压缩功能由名为 thread/sql/compress_gtid_table 的专用前台线程执行。 该线程使用SHOW PROCESSLIST无法查看,但它可以在performance_schema.threads表中查看到(线程 thread/sql/compress_gtid_table 大多数时候都处于休眠状态,直到每满gtid_executed_compression_period个事务之后,该线程被唤醒以执行前面所述的对mysql.gtid_executed表的压缩。然后继续进入睡眠状态,直到下一次满gtid_executed_compression_period个事务,然后被唤醒再次执行压缩,以此类推,无限重复此循环。但如果当关闭binlog或者启用binlog但关闭log_slave_updates变量时,gtid_executed_compression_period变量被设置为了0,那么意味着该线程会始终处于休眠状态且永不会唤醒),如下所示:
SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G;
*************************** 1. row ***************************
THREAD_ID: 26
NAME: thread/sql/compress_gtid_table
TYPE: FOREGROUND
PROCESSLIST_ID: 1
PROCESSLIST_USER: NULL
PROCESSLIST_HOST: NULL
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
PROCESSLIST_TIME: 1509
PROCESSLIST_STATE: Suspending
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: NULL
THREAD_OS_ID: 18677
6. ndb_binlog_index
该表提供查询ndb集群引擎相关的统计信息,由于国内较少使用NDB存储引擎,这里不做过多介绍,有兴趣的朋友可自行研究。
三、 日志记录表
1. 日志信息概述
MySQL的日志系统包含:general query log、slow query log、error log(记录MySQL Server启动时、运行中、停止时的错误信息)、binary log(记录MySQL Server运行过程中的数据变更的逻辑日志)、relay log(记录从库IO线程从主库获取的主库数据变更日志)、DDL log(记录DDL语句执行时的元数据变更信息。5.7中只支持写入到文件,8.0中支持写入到innodb_ddl_log表中,注意,ddl log与online ddl的alter log不同,不要搞混了),其中,在MySQL 5.7中,只有general query log、slow query log支持写入到表中(也支持写入到文件中),其他日志类型在MySQL 5.7版本中只支持写入到文件中,所以,下文中对于日志系统表主要介绍 general query log、slow query log表。
默认情况下,除Windows上的错误日志之外,其他平台的所有日志默认情况下不启用 (DDL日志只在需要时创建,并且无用户可配置选项)。
默认情况下,所有日志均写在datadir目录下,但可以使用每种日志对应的路径参数自行更改路径。
-
general query log:general_log_file=/home/mysql/data/mysqldata1/mydata/localhost.log
-
error log: log_error=/home/mysql/data/mysqldata1/log/error.log
-
slow query log:slow_query_log_file=/home/mysql/data/mysqldata1/slowlog/slow-query.log
-
binary log:log_bin_basename=/home/mysql/data/mysqldata1/binlog/mysql-bin、log_bin_index=/home/mysql/data/mysqldata1/binlog/mysql-bin.index
-
relay log:relay_log_basename=/home/mysql/data/mysqldata1/relaylog/mysql-relay-bin、relay_log_index=/home/mysql/data/mysqldata1/relaylog/mysql-relay-bin.index
默认情况下,所有的日志都写入到磁盘文件,但general query log和slow query log可以通过log_output=TABLE设置保存到表mysql.general_log和mysql.slow_log表中(DDL log在8.0中可以配置,可打印到错误日志中,也可以保存在表innodb_ddl_log中)。
默认情况下,binary log根据max_binlog_size参数设置的大小自动滚动、relay log根据max_relay_log_size或者max_binlog_size自动滚动(如果max_relay_log_size没设置就按照max_binlog_size大小滚动),其他的日志类型不会滚动,总是使用同一个文件,所以其他日志类型增长过大之后,需要自行做切割。
-
一般使用mv file file.bak;然后执行刷新命令,刷新命令可以登录实例使用flush logs命令刷新重新产生新的日志文件,但是该命令是刷新所有的日志类型,要针对具体的日志类型,可以使用:flush binary logs;刷新二进制日志、flush error logs;刷新错误日志、flush general logs;刷新普通查询日志、flush slow logs;刷新慢查询日志、flush relay logs;刷新中继日志、flush engine logs;刷新存储引擎相关的任何可刷新的日志。
-
也可以使用Server的flush tables;语句或者flush table with read lock;语句。
-
刷新操作也可以使用一些命令行工具的选项实现,例如:使用mysqladmin命令的flush-logs选项,或者mysqldump的flush-logs选项与--master-data选项。
2. 日志表实现具有以下特征
- 通常,日志表的主要目的是为程序提供一个访问接口,以便查看Server内的SQL运行情况,所以,日志记录存放在表中比存放在磁盘文件中会更加方便,因为存储在表中可以远程访问这些日志记录,而不需要登录操作系统去访问磁盘文件。
- 日志表可以使用CREATE TABLE,ALTER TABLE和DROP TABLE语句,但前提是需要先使用对应的开关关闭掉表,不能在使用期间操作(例如:set global general_log=0,然后操作general_log表)。
- general_log和slow_log表默认是CSV引擎,使用逗号分割的格式来存放日志记录,CSV数据文件可以很方便地导入其他程序进行处理,例如:excel电子表格。
- 日志表可以修改引擎为MyISAM,但修改之前必须先停止表的使用。合法的引擎为CSV和MyISAM,其他引擎不支持。
- 要禁用日志记录表以便进行相应的DDL语句操作,可以使用以下步骤(以慢查询表为例进行说明,slow_log和general_log表操作方式类似)。
1 2 3 4 |
|
可以使用TRUNCATE TABLE来清空日志记录。
可以使用RENAME TABLE来实现日志表的归档,新旧表做一个原子的名称互换操作,如下:
1 2 3 4 |
|
注意事项
-
可以使用CHECK TABLE语句。
-
不能使用LOCK TABLE语句。
-
不能使用INSERT,DELETE和UPDATE语句,日志表的记录变更由Server内部维护,不能手动操作。
-
FLUSH TABLES WITH READ LOCK和read_only系统变量的设置对日志表没有影响。Server内部始终可以写日志表。
-
日志表的数据变更操作不会记录到binlog,因此不会被复制到从库。
-
可以使用FLUSH TABLES或FLUSH LOGS语句来刷新日志表或日志文件。
-
日志表不支持分区表。
mysqldump转储包含了重新创建这些表的语句,以便在重新加载转储文件后恢复日志表结构,但是日志表中的记录内容不会被转储。
PS:MySQL的查询日志、错误日志等是使用明文记录的,所以,这些日志中有可能会记录用户的明文密码信息,可以使用rewrite插件来使用原始格式记录,详见链接:
-
https://dev.mysql.com/doc/refman/5.7/en/plugin-types.html#query-rewrite-plugin-type
-
https://dev.mysql.com/doc/refman/5.7/en/rewriter-query-rewrite- plugin.html
四、 日志表详解
1. general_log
该表提供查询普通SQL语句的执行记录信息,用于查找客户端到底在服务端上执行了什么SQL(当然,还可以使用企业版的audit log审计插件记录,本文不做赘述,有兴趣的童鞋自行研究)。
该表中的信息在SQL开始执行时就会进行记录,而不是等待SQL执行结束才记录。
下面是该表中存储的信息内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
表字段含义。
-
event_time:查询日志记录到表的那一刻的log_timestamps系统变量值,用于标记查询日志记录何时入库。
-
user_host: 表示该查询日志记录的来源,其中有用户名和主机名信息。
-
thread_id:表示该查询日志记录执行时的process_id。
-
server_id:表示执行该查询的数据库实例ID。
-
command_type:表示该查询的command类型,通常都为query。
-
argument:表示执行查询的SQL语句文本。
mysqld按照接收请求的顺序将语句写入查询日志中(这可能与它们的执行顺序不同)。
在主从复制架构中。
-
主库上在使用基于语句的日志格式时,从库在在重放这些语句之后,会把这些语句记录自己的查询日志中(需要从库启用了查询日志记录功能),使用语句格式记录的binlog在使用mysqlbinlog命令解析之后导入数据库中时,如果实例开启了查询日志记录功能,则这些解析语句也会被记录到查询日志中。
-
主库上使用基于row日志格式时,从库重放这些数据变更之后,这些语句不会被计入从库的查询日志中。
-
在主库上使用基于mixed日志格式时,如果主库是以语句格式记录的,则从库重放这些数据变更之后会把语句记录到自己的查询日志中(需要从库启用了查询日志记录功能),如果主库在记录binlog时被转换为了row格式,则也跟row格式复制一样,从库重放这些数据变更之后不会把这些语句记录到自己的查询日志中。
查询日志可以使用系统变量sql_log_off变量动态关闭当前会话或者所有会话的查询日志记录功能(与sql_log_bin系统变量的作用类似)。
查询日志开关general_log变量和查询磁盘日志文件路径general_log_file变量都可以动态修改(如果已经有查询日志处于打开状态,则使用general_log_file变量修改查询日志路径时关闭旧的查询日志,打开新的查询日志),当启用查询日志时,查询日志将保持到系统变量log_output指定的目的地。
如果启用了查询日志,则Server重新启动的时候会重新打开查询日志文件,如果查询日志存在,则直接重新打开,如果查询日志不存在,则重新创建,如果需要再Server运行时动态归档查询日志,则可以按照如下命令操作(linux或者unix平台)。
1 2 3 4 |
|
也可以在Server运行时通过语句先关闭查询日志功能,然后使用外部命令来归档,然后再重新启用查询日志,这样就不需要使用flush-logs命令来刷新日志文件了,此方法适用于任何平台,命令如下:
1 2 |
|
默认情况下,在Server中执行的语句如果带了用户密码,会被Server重写该语句之后再写入到查询日志中,如果需要记录明文密码,则需要使用--low-raw选项启动Server(使用该选项会绕过密码重写功能),通常不建议记录密码明文信息到查询日志中,因为不安全,但如果有必要,自行判断(例如:需要查询原始的语句信息来排查问题时)。
-
如果带密码的语句中,指定了密码是一个hash值时,则密码字符串不会被重写,例如:CREATE USER 'user1'@'localhost' IDENTIFIED BY PASSWORD 'not-so-secret';就会被原本原因地记录下来,但是如果去掉PASSWORD关键字CREATE USER 'user1'@'localhost' IDENTIFIED BY 'not-so-secret';,则在查询日志中就会被重写为:CREATE USER 'user1'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS ''。
一些语法错误的SQL默认情况下也不会被记录到查询日志中,使用--low-raw选项启动Server会记录所有的原始SQL语句。
查询日志表中的时间戳信息来源于系统变量log_timestamps(包括慢查询日志文件和错误日志文件中的时间戳都来自此系统变量的值),该时间戳值在查询时可以使用CONVERT_TZ()函数或通过设置会话将从这些表中的时间戳信息从本地系统时区转换为任何所需时区(修改会话级别的time_zone变量值)。
2. slow_log
该表提供查询执行时间超过long_query_time设置值的SQL,或者未使用索引的(需要开启参数log_queries_not_using_indexes=ON)或者管理语句(需要开启参数log_slow_admin_statements=ON)。
下面是该表中存储的信息内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
表字段含义。
-
start_time:慢查询日志记录到表时的log_timestamps系统变量值。
-
user_host:带用户名和主机名(IP)格式的值,用于标记访问来源。
-
query_time:慢查询语句总的执行时间。
-
lock_time:慢查询语句持有锁的时间。
-
rows_sent:慢查询语句最终返回给客户端的数据记录数。
-
rows_examined:慢查询语句在存储引擎中的检查记录数。
-
db:慢查询语句执行时的默认库名。
-
last_insert_id:通常为0。
-
insert_id:通常为0。
-
server_id:产生慢查询语句的server id。
-
sql_text:慢查询日志的语句文本。
-
thread_id:产生慢查询日志的线程process_id。
慢查询日志包含了执行时间超过long_query_time系统变量设置的秒数的SQL语句,并且包含了需要检查行数超过min_examined_row_limit系统变量设置的值的SQL语句(默认情况下该变量为0,表示不限制检查行数)。long_query_time的最小值和默认值分别为0和10(单位秒)。该值可以指定为微秒(使用小数),但微秒单位只对记录到文件有效。对于记录到表中的慢查询语句,不支持微秒,微秒部分被忽略。
默认情况下,慢查询日志不会记录管理语句,也不会记录未使用索引的语句,但可以使用log_slow_admin_statements和log_queries_not_using_indexes系统变量更改默认行为,使MySQL Server把管理语句和未使用索引的语句也一并计入慢查询日志。
慢查询日志中语句获取初始锁的时间不计入执行时间,包含时间范围为:获取锁之后,并在语句执行完成之后,将锁释放之前。然后将慢查询语句写入慢查询日志中。所以,在慢查询日志中记录的顺序可能与MySQL Server接收到的语句顺序(执行顺序)并不相同,因为可能有的先执行的语句最后才释放完所有的锁,有的后执行的语句先释放完所有的锁。
默认情况下,慢查询日志不启用。要启用可以使用--slow_query_log =1进行设置,要指定慢查询日志文件名称,可以使用--slow_query_log_file = file_name进行设置,要指定慢查询日志输出目标,可以使用--log-output=FILE|TABLE|NONE 进行设置。
-
如果启用了慢查询日志记录功能,但是未指定名称,则默认在datadir下命名为host_name-slow.log,如果使用--log-output=TABLE 设置了报错在表中,则slow_query_log_file = file_name 设置的路径无效。
-
要动态修改慢查询日志文件名称,可以使用slow_query_log=0先关闭慢查询日志文件,然后使用slow_query_log_file=new_file_name指定新的慢查询日志文件名,然后使用slow_query_log=1重新启用慢查询日志日志文件。
-
如果mysqld在启动是使用了--log-short-format选项,则MySQL Server会将较少的慢查询信息写入慢查询日志中。
如果使用了log_slow_admin_statements=1 设置,则MySQL Server会在慢查询日志中记录如下管理语句:
ALTER TABLE,ANALYZE TABLE,CHECK TABLE,CREATE INDEX,DROP INDEX,OPTIMIZE TABLE和REPAIR TABLE
如果使用了log_queries_not_using_indexes=1 设置,则MySQL Server会把任何不使用索引的查询语句记录到慢查询日志中。
-
当记录这些查询语句时,慢查询日志可能会迅速增长。此时可以通过设置log_throttle_queries_not_using_indexes系统变量来限制这些未使用索引的语句计入慢查询日志的速率(注意:该变量限制的是60秒内的未使用索引的语句数量,不是限制时间)。默认情况下,这个变量是0,表示没有速率限制。当启用限制时,第一个不使用索引的查询执行之后,将打开一个60秒的时间窗口,在该窗口内,将禁止其他未使用索引的查询记录到慢查询日志中,等待时间窗口结束之后,Server记录一个摘要信息,表示有多少次以及在这些执行次数总的花费时间。然后进入下一个60秒的窗口。
MySQL Server按照以下顺序来判断语句是否需要计入慢查询:
-
判断参数 log_slow_admin_statements是否启用,如果启用,则判断语句是否是管理语句,如果是 则计入慢查询,不是则进入下一轮判断。如果参数未启用,则进入下一步判断。
-
判断查询语句执行时间是否超过long_query_time秒,如果超过则计入慢查询,如果未超过,则判断log_queries_not_using_indexes 参数是否启用,如果启用该参数且该语句未使用索引,则计入慢查询,否则进入下一步判断。
-
如果min_examined_row_limit变量设置非零值,则判断语句的检查行数是否超过该变量设置的值,如果超过则计入慢查询,如果未超过则不记录慢查询。
慢查询日志记录的时间戳由log_timestamps系统变量控制。
默认情况下,复制架构中的从库不会将重放binlog产生的慢查询写入自己的慢速查询日志中,如果需要记录从库重放binlog的慢查询语句计入慢查询日志,需要启用变量log_slow_slave_statements=1。
写入慢查询日志的语句中的密码被服务器重写,不会以纯文本形式出现。如果需要记录原始语句,需要使用--log-raw选项。
三、混杂表
由于本系列不介绍企业版认证插件的audit_log_filter, audit_log_user表、防火墙插件的firewall_users, firewall_whitelis表,所以只剩下一个servers混杂表的篇幅不足够另起一期,所有我们强塞到本期里,主要是federated引擎使用的信息,如无兴趣可直接跳过本期后续内容。
1. servers
该表提供查询连接组合信息(远程实例的IP、端口、帐号、密码、数据库名称等信息,详见后续示例),这些连接组合信息通常用于federated引擎(当然也可以作为在数据库中保存连接组合的一种方式,维护也较为方便),该表中的信息需要使用create server方式创建。
在介绍别字段含义之前,先看看dederated引擎的两种创建方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
下面是该表中存储的信息内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
federated引擎的两种使用方式读写远程实例数据示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
|
表字段含义。
- Server_name:连接组合唯一标识(即名称,使用drop server删除连接组合记录时,直接指定该表中存在的server_name即可删除组合记录,如:drop server server_name;)。
- Host:连接组合中的远程主机名(IP或域名),对应create server中的HOST,对应CONNECTION连接组合字符串中的host_name。
- Db:连接组合中的远程实例的数据库名称,对应create server中的DATABASE ,对应CONNECTION连接组合字符串中的db_name。
- Username:连接组合的远程实例用户名,对应create server中的USER ,对应CONNECTION连接组合字符串中的user_name。
- Password:连接组合的远程实例用户密码,对应create server中的PASSWORD ,对应CONNECTION连接组合字符串中的password。
- Port:连接组合的远程实例端口,对应create server中的PORT ,对应CONNECTION连接组合字符串中的port_num。
- Socket:连接组合的本地实例的socket路径,对应create server中的SOCKET ,对应CONNECTION连接组合字符串中的host_name。
- Wrapper:类似一个协议名称,对应create server中的WRAPPER ,对应CONNECTION连接组合字符串中的scheme。
PS:CONNECTION字符串方式不会在mysql.servers表中添加记录。
参考
https://dev.mysql.com/doc/refman/5.7/en/replication-gtids-concepts.html#replication-gtids-gtid-executed-table
https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html
https://dev.mysql.com/doc/refman/5.7/en/federated-create-server.html
https://dev.mysql.com/doc/refman/5.7/en/server-logs.html
http://blog.itpub.net/28218939/viewspace-2661510/