Chapter 16 Replication
复制允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。默认情况下复制是异步的;从服务器不需要永久连接就可以从主服务器接收更新。根据配置,您可以复制所有数据库、选定的数据库,甚至是选定的表。
复制的优点包括:
- 扩展方案 - 将负载分散到多台机器,以提升性能。所有的写操作必须在主服务器上,读操作可以在一个或多个从服务器上。这种模式下,因为主服务专注于写操作,所以可以提升写操作的性能;因为多个从服务器都可以处理读操作,所以可以显蓍的提升读操作的性能。
- 数据安全 - 因为数据被复制到从服务器,而从服务器可以暂停复制过程,所以可以在从服务器上执行备份,而不影响主服务器。
- 分析 - 数据的分析可以在从服务器上执行,从而不影响主服务器的操作。
- 远程数据备份 - 您可以使用复制为远程机器创建数据副本,而不需要对主服务器进行永久访问。
有关如何在此类场景中使用复制,详见:Section 16.3 Replication Solutions。
MySQL 5.7支持不同的复制方法。传统的方法是从主服务器的二进制日志中复制事件,需要指定日志文件和位置。新的方法基于全局事务标识符(GTIDs),不需要指定日志文件和位置,大大简化了常见的复制任务。
MySQL复制支持多种同步类型。传统的同步类型是单向的、异步的复制,其中有一个主服务器,一个或多个从服务器。这与NDB集群的同步复制形成了对比。MySQL支持半同步复制。在半同步复制中,某客户端向主服务提交一个事务时,要至少有一个从服务器回应“已经接收到并记录下了该事务”,主服务才会响应客户端“事务已提交”,否则会阻塞。MySQL 5.7还支持延迟复制,即从服务器会故意滞后于主服务器一段指定的时间。
MySQL复制有许多解决方案,使用哪种方案最佳取决于数据和使用的引擎类型。
复制格式有两种核心类型,基于语句的复制和基于行的复制,前者复制整个SQL语句,后者只复制更改后的行。您还可以使用第三种类型:混合复制。
您可以使用复制来解决许多问题,包括性能、备份,以及作为提高可用性的更大解决方案的一部分。
16.1 Configuring Replication
本节描述各种复制类型的配置方法,包括复制环境所需的设置和配置,包括创建一个复制环境的逐步说明。
16.1.1 Binary Log File Position Based Replication Configuration Overview
本节描述基于二进制日志文件位置的复制,其中主服务器将更改作为“事件”写入二进制日志。从服务器读取二进制日志,并在从服务器的本地数据库上执行二进制日志中的事件。
每一个从服务器,都会接收完整的二进制日志的副本。决定应该执行二进制日志中的哪些语句由是从服务器的责任。除非另外指定,否则主服务二进制日志中的所有事件都会在从服务器上执行。如果需要,可以指定只执行特定数据库或特定表的事件。
注意,无法指定仅接收特定数据库或表的事件。
每个从服务器都保存二进制日志的坐标:已经读取并处理到了哪个日志文件的哪个位置。也就是说,连接到一个主服务的多个从服务,可能在执行二进制日志中的不同部分。因为这个过程由从服务器控制,所以从服务器是否与主服务器连接,不会影响主服务器的操作。因为从服务器记录了已经处理到的二进制日志的坐标,所以从服务器可以断开连接后,过段时间再重连,继续执行。
主服务器和每个从服务器必须配置一个惟一的ID(使用server-id选项)。此外,每个从服务器必须配置主服务器的主机名、日志文件名和该日志文件中的位置,这些配置通过在从服务器上执行CHANGE MASTER TO语句来实现。这些信息存储在从服务器的“主服务信息存储库”中,可以是文件或表。
16.1.2 Setting Up Binary Log File Position Based Replication
本节描述如何设置MySQL服务器来使用基于二进制日志文件位置的复制。
无论采用哪种方法,如下步骤都是通用的:
在主服务器上,启用二进制日志,并配置一个唯一的server ID,这可能需要重启服务器使配置生效。
在每个从服务器上,配置一个唯一的server ID,同样,这可能需要重启服务器使配置生效。
可选的,创建一个单独的用户,专用于复制。
在创建数据快照或启动复制过程之前,您应该记录主服务器上二进制日志的当前位置。在配置从服务器时需要此信息,以便从服务器知道从何处开始执行事件。
如果复制之前,主服务器上已经有了数据,并且想要同步到从服务器上,需要创建数据快照,并拷贝到从服务器上。使用的存储引擎会影响创建快照的方式。在使用MyISAM时,必须使主服务器停止处理SQL,以获得读锁,然后获取其当前的二进制日志坐标并转储其数据,然后才允许主服务器继续执行SQL。如果不停止SQL的执行,则数据转储和主服务器状态信息将不匹配,从而导致从服务器上的数据库不一致或损坏。如果使用的是InnoDB引擎,则不需要这样,事务机制可以保障数据一致性。
把主服务器的信息配置到从服务器上,如主机名(或IP),登录时使用的用户名密码,二进制日志的当前文件名和位置等。
注意,某些步骤需要SUPER权限。
在执行了通用的步骤之后,根据你的具体情况,进行如下不同的操作:
- 主服务器、从服务器都是新安装的,不包含任何数据,详见:Section 16.1.2.5.3 Setting Up Replication between a New Master and Slaves
- 主服务器中已经包含数据,详见:Section 16.1.2.5.4 Setting Up Replication with Existing Data
- 向一个已经在运行的复制环境中,添加从服务器,详见:Section 16.1.2.6 Adding Slaves to a Replication Environment
管理复制环境中的服务器,详见:Section 13.4.1 SQL Statements for Controlling Master Servers 和 Section 13.4.2 SQL Statements for Controlling Slave Servers。
想要更加熟悉复制的配置这程,详见:Section 16.1.6 Replication and Binary Logging Options and Variables。
16.1.2.1 Setting the Replication Master Configuration
对于主服务器,要配置为使用基于二进制日志文件位置的复制,必须启用二进制日志并配置一个惟一的server ID,配置后需要重启生效。二进制日志必须打开,因为它是将修改内容由主服务器,复制到从服务器的基础。
复制环境中的每个服务器,都需要配置一个唯一的server ID,这个值作为各服务器的唯一标识,必须是 1 至 (232)−1之间的正整数。
若要启用二进制日志,和配置server ID,修改my.cnf文件(如果是windows,则是my.ini文件)。如下所示,启用二进制日志,并且二进制日志文件的名称前缀为“mysql-bin”,将server ID配置为1:
[mysqld]
log-bin=mysql-bin
server-id=1
修改完my.cnf文件后,重启mysql server。
注意:
- 如果未配置server-id,或者将其设置为其默认值0,则主服务器会拒绝所有从服务器的连接。
- 对于InnoDB引,为了最大程度的耐久性和一致性,设置如下两个选项:innodb_flush_log_at_trx_commit=1和sync_binlog=1。
- 确保主服务器上未开始skip-networking,否则网络被标用,从服务器无法与主服务器进行通信。
16.1.2.2 Creating a User for Replication
每个从服务器连接主服务器时,都必须使用一组用户名、密码,所以,在主服务器上,必须存在一个这样的用户。在从服务器上,CHANGE MASTER TO命令的MASTER_USER选项,用于指定此用户名。这个用户需要拥有REPLICATION SLAVE权限。你可以为每一个从服务器指定一个用户,也可以仅指定一个用户,用于所有的从服务器。
尽管为复制单独创建一个用户,不是必须的,但是要考虑到,复制所用的用户在主服务信息文件或表中存储时,是明文的。因此,出于安全方面考虑,创建一个仅具有复制权限的独立帐户是有益处的。
创建一个新的用户,使用CREATE USER语句。为此用户授予复制权限,使用GRANT语句。如果某用户是专用于复制的,那么它只需要REPLICATION SLAVE权限。
例如,创建一个名为repl的用户,不限制其主机(也即在任何主机上的从服务器都可以使用此用户登录主服务器),命令如下:
mysql> CREATE USER 'repl'@'%.example.com' IDENTIFIED BY 'password';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.example.com';
关于用户操作的更多详细信息,见:Section 13.7.1 Account Management Statements。
16.1.2.3 Obtaining the Replication Master Binary Log Coordinates
配置从服务器时,需要为其指定,从主服务器的二进制日志中的哪个位置开始复制事件。所以,需要知道主服务器当前二进制日志文件的名称和位置。
警告:FLUSH TABLES WITH READ LOCK语句,会阻塞InnoDB表的COMMIT操作。
如果您计划关闭主服务器以创建数据快照,您可以选择跳过此过程,并将二进制日志索引文件的副本与数据快照一起存储。
在这种情况下,主服务器在重启时创建一个新的二进制日志文件。从服务器从这个新文件开始复制即可。
按以下步骤,要获取主服务器二进制日志当前位置:
1. 使用客户端登录主服务器,在当前会话中,刷新所有的表,并阻止写操作。命令如下:
mysql> FLUSH TABLES WITH READ LOCK;
注意,保持该会话,如果退出这个会话,锁也就自动释放了。
2. 打开一个新的话会,通过如下命令,获取当前的二进制文件名和位置:
mysql > SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 | 73 | test | manual,mysql |
+------------------+----------+--------------+------------------+
File字段显示的是文件名,Position字段显示的位置。将这两个值记录下来,后续配置从服务器时,需要这两个值。这两个值表示,从服务器复制过程的起始点。
如果主服务器之前并未启用二进制日志,那么上述命令看到的结果空的,这种情况下,配置从服务器时文件名指定为空字符串(''),位置指定为4即可。
至此已经获取到了二进制日志坐标,接下来执行什么操作,取决于主服务器中是否已经存在数据。具体如下:
- 如果在开始复制之前,需要将现有数据同步到从服务器,请保持第1步的会话继续运行,以便阻止任何更改,使得复制到从属服务器的数据与主服务器保持同步。然后继续如下步骤:Section 16.1.2.4 Choosing a Method for Data Snapshots。
- 如果在开始复制之前,主服务器上无数据,或者不需要同步这些数据,则现在可以退出第1步打开的会话,然后继续如下步骤:Section 16.1.2.5.3 Setting Up Replication between a New Master and Slaves。
16.1.2.4 Choosing a Method for Data Snapshots
如果在开始复制之前,主服务器上已经存在数据,并且希望同步到从服务器,有多种不同的方法,可用于导出主服务器的数据。具体如下:
- 使用mysqldump创建一个全部数据的备份。这是推荐的方式,尤其是在使用InnoDB引擎时。
- 如果您的数据库存储在二进制可移植文件中,则可以将原始数据文件复制到一个从服务器中。这可能比使用mysqldump并在每个从属节点上导入文件更有效,因为它可以跳过在INSERT语句重放时更新索引的开销。对于InnoDB这样的存储引擎,不推荐这样做。
16.1.2.4.1 Creating a Data Snapshot Using mysqldump
使和mysqldump创建备份完成后,在开始复制之前,将备份导入到从服务器上。
如下示例中,导出全部数据到dump.db文件中,命令中加入了--master-data选项,作用是自动添加CHANGE MASTER TO语句,从服务器会用此语句开始复制过程。
shell> mysqldump --all-databases --master-data > dbdump.db
注意,如果没有加--master-data选项,则需要在执行上述命令前,在另一个会话中执行FLUSH TABLES WITH READ LOCK命令,锁定所有表。加了--master--data选项,会自动锁定所有表。
可以在导出数据时排除某些数据库,具体请查询mysqldump文档:Section 4.5.4 mysqldump — A Database Backup Program。
16.1.2.4.2 Creating a Data Snapshot Using Raw Data Files
本节描述如何使用原始文件创建数据快照。对使用具有复杂缓存或日志算法的存储引擎的表,使用这种方法需要额外的步骤来生成完美的“时间点”快照:初始复制命令可能会遗漏缓存信息和日志更新,即使您获得了全局读锁。存储引擎如何应对这种情况取决于它的崩溃恢复能力。
如果使用InnoDB表,可以使用MySQL Enterprise Backup组件中的mysqlbackup命令生成一致的快照。商业特性,略。。。
可以考虑使用percona代替此商业特性。
16.1.2.5 Setting Up Replication Slaves
本章节描述,如何配置从服务器。开始之前,确保已经完成如下步骤:
确保主服务器已按照如下章节配置完成:Section 16.1.2.1 Setting the Replication Master Configuration。
获取主服务器状态信息:Section 16.1.2.1 Setting the Replication Master Configuration。
在主服务器上,释放锁:
mysql> UNLOCK TABLES;
16.1.2.5.1 Setting the Replication Slave Configuration
每个从服务器,都需要配置一个唯一的server ID。配置方法:
[mysqld]
server-id=2
此配置需要重启生效。
注意:如果未设置server ID,或者将其设置为默认值0,则从服务器拒绝连接到主服务器。
在从属服务器上启用二进制日志,不是必须的。但是,如果开启了,那么可以使用从服务器的二进制日志进行数据备份、故障恢复,或者可以将其他的从服务器挂到该服务器上(该服务器也就成了另一个从服务的主)。
16.1.2.5.2 Setting the Master Configuration on the Slave
使用如下语句,配置从服务器与主服务器之间的连接信息。
mysql> CHANGE MASTER TO
-> MASTER_HOST='master_host_name',
-> MASTER_USER='replication_user_name',
-> MASTER_PASSWORD='replication_password',
-> MASTER_LOG_FILE='recorded_log_file_name',
-> MASTER_LOG_POS=recorded_log_position;
注意,复制过程中,从服务器连接主服务器,必须使用TCP/IP,不能使用UNIX socket文件。
CHANGE MASTER TO还有其他选项。比如可以配置SSL。更多信息见:Section 13.4.2.1 CHANGE MASTER TO Syntax。
下一步执行什么操作,取决于是否要将主服务中的已存数据导入从服务中。具体如下:
- 如果没有数据,需要导入,后续按如下章节操作:Section 16.1.2.5.3 Setting Up Replication between a New Master and Slaves。
- 如果有数据,需要导入,后续按如下章节操作:Section 16.1.2.5.4 Setting Up Replication with Existing Data。
16.1.2.5.3 Setting Up Replication between a New Master and Slaves
启动复制之前,没有需要导入到从服务器中的数据,那么现在可以开始启动从服务器上的复制程序了。
16.1.2.5.4 Setting Up Replication with Existing Data
在开始复制之前,将数据快照从主服务器传输到从服务器,将数据快照导入到从服务器。导入数据的具体方法,要看该数据快照是怎样制作的。
如果是使用mysqldump制作的,按如下步骤导入:
1. 启动从服务器,使用--skip-slave-start选项,该选项作用是启动mysql服务时不启动复制过程。
2. 导入数据,命令如下:
shell> mysql < fulldb.dump
如果是使用原始文件制作的备份,按如下步骤导入:
1. 提取文件:
shell> tar xvf dbdump.tar
2. 启动从服务器,使用--skip-slave-start选项,该选项作用是启动mysql服务时不启动复制过程。
3. 配置从服务器,使用CHANGE MASTER TO语句,详见:Section 16.1.2.5.2 Setting the Master Configuration on the Slave。
4. 启动复制线程。
mysql> START SLAVE;
执行完上述步骤后,从服务器开始连接到主服务器,开始复制。
如果主服务器上没有配置正确的server ID,那么从服务器无法连接到它。同样的,如果从服务器没有配置正确的server ID,错误日志中会有如下打印:
Warning: You should set server-id to a non-0 value if master_host
is set; we will force server id to 2, but this MySQL server will
not act as a slave.
从服务器,会将主服务器的信息存入“主服务信息库”中,可以是文件,也可以是表,由--master-info-repository决定。--master-info-repository=FILE,则会在数据目录生成两个文件:master.info、relay-log.info。--master-info-repository=TABLE,则信息存储在mysql.master_slave_info表中。无论使用哪种配置,都不要手动修改对应的文件或表。要修改这些内容,应该使用CHANGE MASTER TO语句。
一个主服务器的快照就足以创建多个从服务器。要设置更多的从服务,使用相同的主快照,并按照上述过程操作即可。
16.1.2.6 Adding Slaves to a Replication Environment
可以向已存在的复制结构中,添加新的从服务,而不需要停止主服务。为此,可以通过复制现有从服务的数据目录来设置新的从服务,并为新的从服务提供不同的server ID(由用户指定)和server UUID(在启动时生成)。
要复制现有的从服务:
1. 停止从服务的复制过程,并记录从服务状态信息,特别是主服务二进制日志文件和中继日志文件的位置。有两种方法可以获取该信息,第一种是查看Performance Schema中的表数据,详见:Section 25.12.11 Performance Schema Replication Tables。第二种是执行SHOW SLAVE STATUS命令。
mysql> STOP SLAVE;
mysql> SHOW SLAVE STATUS\G
2. 停止从服务器实例。
shell> mysqladmin shutdown
3. 将该从服务的数据目录拷贝到新的从服务,包含日志文件和中继日志文件。
重要:
- 拷贝之前,与该服务相关的所有文件都存储在数据目录中。例如,InnoDB system表空间、undo表空间和redo日志可能存储在另一个位置。InnoDB表空间文件和file-per-table表空间可能是在其他目录中创建的。从服务器的二进制日志和中继日志可能在数据目录之外的目录中。检查服务器设置的系统变量,并查找已指定的任何替代路径。如果找到任何目录,将它们也一起复制过去。
- 在复制期间,如果主服务信息库和中继日志信息,是使用文件存储的(详见Section 16.2.4 Replication Relay and Status Logs),这是MySQL 5.7的默认设置。确保将这些文件复制到新的从服务。如果这些信息是存储在表中的,则这些表已经位于数据目录中。
- 完成拷贝之后,删除拷贝过去的auto.cnf文件,这样新的从服务启动时,会自动生成新的server UUID,这个值必须是唯一的。
如下是一个常见的错误:
071118 16:44:10 [Warning] Neither --relay-log nor --relay-log-index were used; so
replication may break when this MySQL server acts as a slave and has his hostname
changed!! Please use '--relay-log=new_slave_hostname-relay-bin' to avoid this problem.
071118 16:44:10 [ERROR] Failed to open the relay log './old_slave_hostname-relay-bin.003525'
(relay_log_pos 22940879)
071118 16:44:10 [ERROR] Could not find target log during relay log initialization
071118 16:44:10 [ERROR] Failed to initialize the master info structure
如果relay_log系统变量未指定,那么中继日志文件名中会包含hostname。类似的,如果relay_log_index系统变量未指定,则中继日志索引文件名中会包含hostname。
为避免这个问题,在新的从服务上,使用与原从服务相同的系统变量relay_log。如果原从服务未设置该变量,则使用其默认值{existing_slave_hostname}-relay-bin。如果这是不可能的,将原从服务的中继日志索引文件复制到新从服务,并在新从服务上设置relay_log_index系统变量,以匹配在原从服务使用的内容。同样的,如果在原从服务上未设置该变量,则使用其默认值{existing_slave_hostname}-relay-bin.index。
4. 当拷贝完成后,重新启动原从服务器。
5. 在新的从服务上,配置server ID。
6. 启动新的从服务,使用--skip-slave-start选项。使用SHOW SLAVE STATUS命令查看新的从服务的配置,与原从服务进行对比,检查是否正确。检查server ID和server UUID是否唯一。
7. 启动复制线程。命令如下:
mysql> START SLAVE;
至此,新的从服务,开始从主服务复制数据。
16.1.3 Replication with Global Transaction Identifiers
本节解释使用全局事务标识符(GTIDs)进行基于事务的复制。GTIDs的英文全称是global transaction identifiers。使用GTIDs时,每个事务都可以被唯一的标识和跟踪,事务在原始服务器上提交,并应用到所有的从服务器。不再需要指定二进制日志文件名及其位置来启动一个从服务。由于基于gtid的复制是完全基于事务的,因此很容易确定主服务器和从服务器是否一致;只要在主服务器上提交的所有事务也在从服务器上提交,就可以保证两者之间的一致性。使用GTID时,复制格式可以是基于语句的,也可以是基于行的,推荐使用基于行的。GTID在主服务和从服务上都会保存。您可以通过检查的二进制日志,来确定应用到从服务器的事务的源。在某服务器上提交的某GTID的事务之后,该服务器将忽略具有相同GTID的任何后续事务。因此,在主服务器上提交的事务最多只能在从服务器上应用一次,这有助于保证一致性。
16.1.3.1 GTID Format and Storage
全局事务标识符(GTID)是一个惟一的标识符,它创建并与源服务器(主服务器)上提交的每个事务相关联。此标识符不仅对原始服务器是唯一的,而且对于给定复制拓扑中的所有服务器都是唯一的。当在主服务器上提交一个事务时,它被分配一个新的GTID,前提是该事务会被写入二进制日志。保证事务具有单调递增的gtid,之间没有间隙。如果一个事务没有被写到二进制日志中(例如,因为事务被过滤掉了,或者事务是只读的),就不会分配GTID。在复制过程中,为事务分配的GTID保持不变。GTID在复制的事务开始执行之前出现,即使复制的事务没有写入从服务器上的二进制日志,或者在从服务器上被过滤掉,GTID也会被持久化。系统变量mysql.gtid_executed用于保存MySQL服务器上应用的所有事务的gtid,但存储在当前活动的二进制日志文件中的事务除外。
GTIDs的自动跳过功能,意味着在主服务器上提交的事务最多只能在从服务器上应用一次,这有助于保证一致性。一旦拥有某个GTID的事务在某服务器上执行过了,那么后续的拥有相同GTID的事务再次提交时,会被忽略。不会报错,但事务中的任何语句都不会执行。
如果带有给定GTID的事务已开始在服务器上执行,但尚未提交或回滚,则使用相同GTID在服务器上启动并发事务将被阻塞。服务器既不开始执行并发事务,也不将控制权返回给客户端。一旦事务的第一次事务提交或回滚,阻塞在同一GTID上的并发会话就可以继续进行。如果第一次事务回滚,则一个并发会话继续尝试事务,而在同一GTID上阻塞的任何其他并发会话仍然被阻塞。如果第一次事务提交,所有具有相同GTID的并发会话将停止阻塞,并自动跳过事务的所有语句。
GTID表示为一对坐标,由冒号分隔,如下所示:
GTID = source_id:transaction_id
source_id用于标识事务的原始服务器。通常,这个值是server UUID。transaction_id是一个序列号,由事务在主服务器上提交的顺序决定。例如,要提交的第一个事务的transaction_id为1,而要在同一个原始服务器上提交的第10个事务的transaction_id为10。transaction_id不会为0。例如,某服务器(该服务器UUID为3E11FA47-71CA-11E1-9E33-C80AA9429562)上提交的第23个事务具有以下GTID:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
事务的GTID显示在mysqlbinlog的输出中,它用于标识Performance Schema复制状态表中的单个事务,例如replication_applier_status_by_worker。由gtid_next系统变量(@@GLOBAL.gtid_next)存储的值是一个GTID。
GTID Sets
GTID集合是由一个或多个GTID或GTID范围组成的集合。在MySQL服务器中以多种方式使用GTID集合。例如,系统变量gtid_executed和gtid_purged中存储的是GTID集合。START SLAVE的子句UNTIL SQL_BEFORE_GTIDS和UNTIL SQL_AFTER_GTIDS,可以用于使一个从服务只处理到GTID集中的第一个GTID,或者在GTID集中的最后一个GTID之后停止。内置函数GTID_SUBSET()和GTID_SUBTRACT(),要以GTID集为输入参数。
来自同一服务器的一个范围的gtid可以折叠成一个表达式,如下所示:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
上面的例子表示了,第1至5个源自server_uuid为3E11FA47-71CA-11E1-9E33-C80AA9429562的服务器的事务。
多个来自同一服务器的单个gtid或gtid范围也可以用一个表达式,gtid或范围用冒号分隔,如下例所示:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49
GTID集还可以包含来自不同服务器的GTID,之间用逗号分隔即可。例如:
2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19
当从系统变量查看GTID集时,uuid按字母顺序排列,数字间隔按升序合并。
GTID集语法如下:
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
uuid:interval[:interval]...
uuid:
hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
[0-9|A-F]
interval:
n[-n]
(n >= 1)
mysql.gtid_executed Table
GTID存储于mysql.gtid_executed表中。该表中的一行包含它所表示的每个GTID或GTID集合的原始服务器的UUID,以及该集合的开始和结束事务id。对于仅引用单个GTID的行,最后两个值是相同的。
mysql.gtid_executed表是在安装或升级MySQL服务器时创建的(如果它还不存在),使用如下所示的语句:
CREATE TABLE gtid_executed (
source_uuid CHAR(36) NOT NULL,
interval_start BIGINT(20) NOT NULL,
interval_end BIGINT(20) NOT NULL,
PRIMARY KEY (source_uuid, interval_start)
)
警告:像其他mysql系统表一样,不要尝试手动创建或修改这个表。
这个表是mysql服务器内部使用的。它使得从服务器可以在未启用二进制日志的情况下使用GTID,使得在二进制日志丢失的情况下,依然可以保存GTID状态。
注意:当执行RESET MASTER语句时,这个表会被清空。
仅当 gtid_mode 为 ON 或者 ON_PERMISSIVE时,GTID才会存储在这个表中。GTID的存储,还与二进制日志的配置有关:
- 如果二进制日志被禁用(log_bin为OFF),或者log_slave_updates被禁用,服务器将每个事务的GTID都存储到该表中。另外,该表中内容会以用户配置的速度定时压缩。该场景仅适用于从服务器,因为复制拓扑中的主服务器的二进制日志一定会开启。
- 如果二进制日志开启(log_bin为ON),每当日志旋转或服务关闭时,服务器会将所有写入前一个二进制日志的事务的gtid写入该表。该场景仅适用于主服务器,或者打开了二进制日志的从服务器。服务异常停止时,当前二进制日志中的GTID未来得及写入该表。在recovery过程中,这些GTID会写入到该表中。有一个例外情况:服务器重启后禁用了二进制日志,在这种情况下,服务器无法访问二进制日志文件来恢复gtid,因此无法启动复制。在启用了二进制日志的情况下,该表中保存的不是全部的GTID。要查看全部的GTID,要使用全局系统变量gtid_executed,这个变量的值会在每次事务提交时更新,所以要查看MySQL GTID状态,应该使用这个全局系统变量,而不是使用mysql.gtid_executed表。
mysql.gtid_executed Table Compression
随着时间的推移,mysql.gtid_executed表会有许多行,这些数据中,来自同一服务器上的多个gtid,可能构成一个范围,如下所示:
+--------------------------------------+----------------+--------------+
| 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 |
...
为节省空间,MySQL周期性的对该表内容进行压缩,将上述多条记录,改为一条记录,如下所示:
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 43 |
...
通过设置gtid_executed_compression_period系统变量,可以控制每执行多少个事务后进行压缩,从而控制压缩速率,该变量默认值为1000,也就是说默认情况下,每执行1000个事务,会进行一次对该表的压缩。将这个值设置为0,表示禁用压缩,这可能导致磁盘空间增长较快。
注意:当二进制日志启用时,gtid_executed_compression_period变量是没有用的,压缩是在每次日志旋转时触发的。
执行压缩,有一个专用的线程,名为thread/sql/compress_gtid_table。使用SHOW PROCESSLIST语句是看不到这个线程的,可以用如下方式查看:
mysql> 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
该线程通常是休眠的,直到gtid_executed_compression_period变量指定的事务个数达到了,这个线程才停止休眠,执行压缩。在禁用二进制日志,并将gtid_executed_compression_period设置为0,意味着该线程总是处于休眠状态,永远不会醒来。
16.1.3.2 GTID Life Cycle
GTID的生命周期包括以下步骤:
- 事务在主服务器上执行并提交。这个客户端事务被分配了一个GTID,它由主服务器的UUID和最小的并且未被使用的非零事务序列号组成。GTID被写入主服务的二进制日志(事务本身的日志之前,紧邻着)。如果一个客户端事务没有写到二进制日志中(例如,因为事务被过滤掉了,或者事务是只读的),那么不会为其分配GTID。
- 如果为事务分配了一个GTID,那么GTID会自动持久化:在事务提交时,将GTID写入到二进制日志中(位置是该事务的开头),作为一个Gtid_log_event。每当二进制日志旋转(指的是新生成一个二进制日志文件)或关闭服务,都会将所有写入前一个二进制日志文件的事务的gtid写入mysql.gtid_executed表。
- 如果为事务分配了GTID,则通过将GTID添加到gtid_executed系统变量中(@@ global.gtid_execution),非原子地外部化GTID(在提交事务后不久)。这个系统变量中的GTID集合包含所有提交事务的GTID,它在复制中用作表示服务器状态的令牌。启用了二进制日志记录的情况下,gtid_executed系统变量中的GTIDs集是应用事务的完整记录,但mysql.gtid_executed表中不是完整记录,因为最近的记录仍然在当前的二进制日志文件中。
- 当二进制日志传输到从服务器,并且存储到从服务器的中继日志后,从服务器读取GTID,并设置gtid_next的值设置为这个GTID。这告诉从服务器下一个事务必须使用这个GTID。需要注意的是,从服务器会在一个会话中设置gtid_next。
- 从服务验证还没有线程使用gtid_next中的GTID来处理事务。处理事务本身之前,首先通过阅读和检查复制事务的GTID,从服务不仅要确保以前没有相同GTID的事务,而且没有其他会话已经读到这个GTID但尚未提交事务。因此,如果多个客户端试图并发地应用相同的事务,服务器只允许其中一个执行。从服务的gtid_owned系统变量(@@GLOBAL.gtid_owned)显示当前使用的每个GTID以及拥有它的线程的ID。如果某个GTID以前已经有过了,虽不会引发错误,但使用自动跳过功能来忽略事务。
- 如果某个GTID之前没有出现过,则从服务器会应用该事务。因为gtid_next被设置为主服务已经分配的GTID,所以从服务并不尝试为这个事务生成一个新的GTID,而是使用存储在gtid_next中的GTID。
- 如果从服务上开启了二进制日志,那么GTID会自动持久化:在事务提交时,将GTID写入到二进制日志中(位置是该事务的开头),作为一个Gtid_log_event。每当二进制日志旋转(指的是新生成一个二进制日志文件)或关闭服务,都会将所有写入前一个二进制日志文件的事务的gtid写入mysql.gtid_executed表。
- 如果从服务上禁用了二进制日志,那么GTID会自动持久化:直接写入mysql.gtid_executed表。mysql会在事务中附加一条语句,用于插入GTID数据。这种情况下,mysql.gtid_executed表中是已应用事务的完整记录。注意,在MySQL 5.7中,将GTID插入表中的操作对于DML语句是原子操作,但对于DDL语句不是,因此,如果服务器在涉及DDL语句的事务之后意外退出,那么GTID状态可能会变得不一致。从MySQL 8.0开始,对于DDL语句和DML语句的都是原子性的了。
- 在将复制的事务提交到从服务器后不久,通过将其添加到从服务器的gtid_executed系统变量中来非原子地外部化GTID。对于主服务,该系统变量表示的是已提交事务的GTID的完整记录。如果在从属服务器上禁用了二进制日志记录,那么在mysql.executed表也是应用于从服务上的事务的完整记录。如果在从服务器上启用了二进制日志记录,这意味着一些gtid只记录在当前的二进制日志中,那么gtid_executed系统变量中的gtid集是惟一的完整记录。
主服务器上完全过滤掉的事务没有分配GTID,因此它们不会添加到gtid_executed系统变量中,也不会添加到mysql.gtid_executed表。但是,从服务器上完全过滤掉的复制事务的gtid依然会记录下来。如果在从服务器上启用了二进制日志记录,则过滤掉的事务将作为Gtid_log_event写入二进制日志,后面跟着一个只包含BEGIN和COMMIT语句的空事务。如果禁用了二进制日志记录,则将过滤掉的事务的GTID写入mysql.gtid_executed表。保留过滤掉的事务的gtid可以确保mysql.executed表和gtid_executed系统变量中的GTID集合可以压缩。它还确保在从服务器重连主服务器时,不会再次检索过滤掉的事务。
如果从服务上启用了多个应用线程(slave_parallel_workers > 0),事务的应用可以是并行的,所以事务可以不按照顺序提交(除非设置了slave_preserve_commit_order=1)。当这种情况发生时,gtid_executed系统变量中的GTID集将包含多个GTID范围,它们之间有间隙。(在主服务或者单应用线程的从服务器上,GTID是单调递增的,无间隙的。)间隙只存在于最近的事务中,随着事务应用的过程,会被填充。当使用STOP SLAVE语句,优雅的停止复制过程时,正在执行的事务会继续,以便填充间隙。如果服务异常导致服务关闭,或者使用KILL语句停止服务,间隙会依然存在。
What changes are assigned a GTID?
典型的场景是服务器为提交的事务生成一个新的GTID。但是,除了事务之外,还可以将gtid分配给其他类型的更改,在某些情况下,单个事务会分配多个gtid。
写入二进制日志的每个数据库更改(DDL或DML)都会分配一个GTID。这包括自动提交的更改,以及使用BEGIN和COMMIT或START TRANSACTION语句提交的更改。数据库以及除去表之外的其他数据库对象如procedure, function, trigger, event, view, user, role, grant的创建、修改、删除,也会分配GTID。
非事务性更新,也会分配GTID。此外,对于非事务性更新,如果在试图写入二进制日志缓存时发生磁盘写操作失败,并且因此导致在二进制日志中出现了一个间隙,则会为产生的事件分配一个GTID。
当二进制日志中生成的语句自动删除表时,将为该语句分配一个GTID。当从服务开始应用来自刚刚启动的主服务器的事件时,当使用基于语句的复制时(binlog_format=STATEMENT),并且具有打开的临时表的用户会话断开连接时,临时表将被自动删除。使用MEMORY存储引擎的表在服务器启动后第一次访问时就会被自动删除,因为在关闭期间可能丢失了数据。
当一个事务没有写到源服务器上的二进制日志时,不会分配GTID。这包括回滚的事务和在原始服务器上禁用二进制日志记录时执行的事务,包括全局的(在服务器配置中指定—skip-log-bin)或会话的事务(SET @@SESSION.sql_log_bin = 0)。也包括使用行复制格式(binlog_format=ROW)时的no-op事务。
为XA事务的XA准备阶段和XA提交或XA回滚阶段分配单独的gtid。XA事务的准备被持久化,以便用户可以提交或在出现故障时回滚(在复制拓扑中可能包括到另一个服务器的故障转移)。因此,事务的两部分是分别复制的,因此它们必须有自己的GTID,即使对于回滚的非XA事务来说,不会分配GTID。
在以下特殊情况下,一条语句可以生成多个事务,因此可以分配多个GTID:
- 如果某个存储过程提交多个事务,为其提交的每个事务生成一个GTID;
- 使用DROP TABLE语句删除多个不同类型的表;
- 复制格式为基于行(binlog_format=ROW)的情况,执行CREATE TABLE ... SELECT语句时,会为CREATE TABLE生成一个GTID,为行的插入生成一个GTID。
The gtid_next
System Variable
默认情况下,对于在用户会话中提交的新事务,服务器将自动生成一个新的GTID。将事务应用于从服务时,将保留来自原始服务器的GTID。您可以通过设置gtid_next系统变量的会话值来更改此行为:
- 当此变量被设置为默认值AUTOMATIC,一个事务提交并写入二进制日志时,会分配一个新的GTID。如果事务回滚了,没有写入二进制日志,则不会分配GTID。
- 当此变量被设置为一个有效的GTID时,服务器将该GTID分配给你的事务。这个GTID被分配并添加到gtid_executed系统变量中,即使事务没有写入二进制日志,或者事务为空时。
请注意,在将gtid_next设置到一个特定的GTID,并且事务已被提交或回滚后,必须在执行任何其他语句之前,显式的执行SET @@SESSION.gtid_next语句。如果你不想再显式地分配更多的GTID,可以将此变量设置回AUTOMATIC。
当应用线程应用事务时,用到了这个技术:通过设置@@SESSION.gtid_next,指定正在应用的事务使用其在源服务器上已分配的GTID。意味着复制过程中,保留了来自源服务器的GTID,而不是由从服务生成新的GTID。还意味着即使在从服务器上禁用了二进制日志或从服务日志更新,或者事务是no-op的,或者从服务器上过滤掉了事务时,也会将GTID添加到从服务器上的gtid_executed系统变量中。
客户端可以通过在执行事务之前设置@@SESSION.gtid_next为指定的GTID,来模拟复制的事务。mysqlbinlog使用此技术生成二进制日志的转储,客户端可以重播该转储以保存gtid。通过客户端提交的模拟事务与通过复制应用线程提交的复制事务完全相同,在事后无法对它们进行区分。
The gtid_purged
System Variable
gtid_purged系统变量(@@GLOBAL.gtid_purged)中的gtid集合,包含所有已在服务器上提交的、但却不存在于任何二进制日志文件中的gtid。gtid_purged变量是gtid_executed变量的子集。如下类别的gtid会在gtid_purged变量中:
- 从服务器上禁用二进制日志的情况下,提交的复制事务的gtid。
- gtid所写入的二进制日志文件被删除时,这些gtid会记入gtid_purged中。
- 通过SET @@GLOBAL.gtid_purged语句,显示的添加到该集合中的gtid。
您可以更改gtid_purged的值,以便告知服务器某些GTID已经应用过了,尽管这些GTID在该服务器的二进制日志中,并未出现过。向gtid_purged中添加gtid时,也同样会添加到gtid_executed变量中。此操作的一个示例用例是:在服务器上还原一个或多个数据库的备份,但是相关二进制日志。在MySQL 5.7中,只有当gtid_executed为空时(此时gtid_purged肯定也为空),才能更改gtid_purged的值。
gtid_executed和gtid_purged系统变量中的gtid集在服务器启动时,进行初始化。每个二进制日志文件都以事件Previous_gtids_log_event开始,该事件包含所有以前的二进制日志文件中的gtid集(由之前文件的Previous_gtids_log_event中的gtid和之前文件中每个Gtid_log_event中的gtid组成)。最老的文件中的Previous_gtids_log_event ,和最近的文件,在服务启动的时候,用于计算gtid_executed和gtid_purged集合:
- gtid_execution的计算方法是,将最近的二进制日志文件中的Previous_gtids_log_event中的gtid、该二进制日志文件中事务的gtid和存储在mysql.gtid_executed表中的gtid合并。此gtid集合,包含所有在此服务上应用的gtid(和显示添加到gtid_purged中的gtid)。无论它们是否包含在此服务的二进制日志文件中。不包含当前正在处理的事务的gtid(@@GLOBAL.gtid_owned)。
- gtid_purged的计算方法是,首先加入在最近的二进制日志文件中Previous_gtids_log_event中的gtid,然后加入在该二进制日志文件中的gtid(结果用gtids_in_binlog表示)。此步骤的结果是:当前或曾经记录在二进制日志中的所有gtid。接下来,从gtids_in_binlog中减去最老的二进制日志文件Previous_gtids_log_event中的gtid(结果用gtids_in_binlog_not_purged表示)。此步骤的结果是:当前记录在二进制日志中的gtid。最后,从gtid_executed中减去gtids_in_binlog_not_purged。此步骤的结果是:应用过,但是目前的二进制日志中未包含的gtid,也即gtid_purged表示的含义。
对于这些计算,如果涉及到来自MySQL 5.7.7或更早版本的二进制日志,那么gtid_executed和gtid_purged的计算结果可能是错误的,即使稍后重新启动服务器,错误依然存在。更多细节,查看binlog_gtid_simple_recovery系统变量的解释,这个变量控制如何迭代二进制日志来计算GTID集。如果上述情况存在,则在启动服务器之前,在服务器的配置文件中设置binlog_gtid_simple_recovery=FALSE。该设置使服务器遍历所有二进制日志文件(不仅仅是最新和最老的),以查找GTID事件开始出现的位置。如果服务器有大量没有GTID事件的二进制日志文件,则此过程可能需要很长时间。
Resetting the GTID Execution History
如果需要重置服务器上的GTID执行历史记录,请使用RESET MASTER语句。例如,在执行测试查询以验证新启用gtid的服务器上的复制设置之后,或者在希望将新服务器加入复制组但其中包含一些不需要的本地事务,而组复制不接受这些事务时,可能需要执行此操作。在执行RESET MASTER之前,确保您备份了二进制日志文件和二进制日志索引文件,并获取和保存gtid_executed系统变量中包含的GTID集(例如,通过执行SELECT @@GLOBAL.gtid_executed语句,并保存结果)。如果要从GTID集中删除不需要的事务,请使用mysqlbinlog检查事务的内容,以确保它们没有值,不包含必须保存或复制的数据,并且不会导致服务器上的数据更改。
执行RESET MASTER时,会执行以下RESET操作:
- gtid_purged系统变量的值会被设置为空字符串。
- 系统变量gtid_executed的全局值(不是会话值),会被设置为空字符串。
- mysql.gtid_executed表被清空。
- 如果服务器打开了二进制日志,则现存的二进制日志文件被删除,二进制日志索引文件被清空。
请注意,RESET MASTER是重置GTID执行历史记录的方法,即使服务器是禁用二进制日志的从服务器。RESET SLAVE不会对GTID执行历史记录产生影响。
16.1.3.3 GTID Auto-Positioning
使用了GTID,主从复制时,就不再需要指定二进制日志文件及位置,从服务所需的所有信息,都可以从复制数据流中获得。不过,需要启用MASTER_AUTO_POSITION选项。详情见:Section 16.1.3.4 Setting Up Replication Using GTIDs。
MASTER_AUTO_POSITION选项默认是关闭的。如果从服务器在进行多源复制,则需要在每个复制通道上都启用此选项。禁用此选项,会使得从服务变为基于二进制日志文件名及位置的复制方式,也就是需要指定MASTER_LOG_FILE 和 MASTER_LOG_POS的方式。
当一个复制奴隶启用了GTIDs(GTID_MODE=ON、ON_PERMISSIVE、OFF_PERMISSIVE),并且启用了MASTER_AUTO_POSITION选项时,将激活 auto-positioning以连接到主服务器。主服务器必须设置GTID_MODE=ON。在初始握手时,从服务发送一个GTID集,其中包含它已经接收、提交的事务。这个GTID集中包含,gtid_executed系统变量中的GTID(@@GLOBAL.gtid_executed),Performance Schema数据库中 replication_connection_status 表中的GTID( SELECT RECEIVED_TRANSACTION_SET FROM PERFORMANCE_SCHEMA.replication_connection_status语句的结果),该表中GTID表示接收到的事务。
主服务器响应内容为:所有记录在其二进制日志中的、不包括在从服务器发送过来的GTID集中的事务。这个交换确保了主服务只发送从服务还没有收到或提交的事务。如果从服务器从多个主服务器接收事务,即钻石拓扑的情况,自动跳过功能(auto-skip)可以确保事务不会被应用两次。如果主程序应该发送的任何事务已经从主程序的二进制日志中清除,或者通过另一种方法添加到gtid_purged系统变量中,则主服务器向从服务器发送一个ER_MASTER_HAS_PURGED_REQUIRED_GTIDS错误,复制不会开始。从服务器无法自动处理这个错误。尝试在不启用MASTER_AUTO_POSITION选项的情况下重新连接,会导致丢失事务。正确的处理方式是,从其他源复制丢失的事务,或者,使用最新的备份,重新创建从服务。考虑修改主服务器上的二进制日志过期时间,以确保不再发生这种情况。
如果交换过程中发现,某些从服务器已经接收或提交的事务,其GTID中server UUID部分是主服务器,但是主服务器本身没有这些事务的记录,则主服务器向从服务器发送一个ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER 错误,复制过程无法开始。如果没有sync_binlog=1的主服务器出现电源故障或操作系统崩溃,已提交的事务尚未同步到二进制日志文件,但却被从服务器接收,就会出现这种情况。如果任何客户机在主服务器重新启动后提交事务,那么主服务器和从服务器可能会出现分歧,因为主服务器和从服务器可能对不同的事务使用相同的GTID。如果相同的GTID现在用于不同的事务,则需要根据需要对单个事务执行手动冲突解决,或者从复制拓扑中删除主服务或从服务。如果问题只是缺少主服务器上的事务,那么可以将主服务器改为从服务器,使它从其他服务器复制数据,然后在需要时再次将其变为主服务器。
16.1.3.4 Setting Up Replication Using GTIDs
本章描述如何配置和启动基于GTID的复制。这是一个冷启动过程。
关键步骤如下:
- 如果复制已经在运行,则通过将两个服务器设置为只读,然后让它们数据同步为一致状态。
- 停止两个服务。
- 启用GTID,进行其他正确配置,重新启动服务。启动服务时的一些必要的选项,会在下面的示例中详述。
- 命令从服务以主服务为复制数据源,并启用auto-positioning。具体语句,下面示例中详述。
- 创建一个新的备份。未启用GTID时的二进制日志无法用于基于GTID复制的从服务器,所以在开启GTID之前做的备份,是无用的。
- 启动复制过程,禁用只读模式,以便服务器可以接收更新请求。
下面的示例中,两台服务器已经在运行复制,不过是基于二进制日志文件位置的复制模式。如果是以两台全新的服务开始,请参见Section 16.1.2.2 Creating a User for Replication了解如何添加专用于复制的用户,参见Section 16.1.2.1 Setting the Replication Master Configuration了解如何设置server_id变量。
多数步骤需要SUPER权限。
第一步,同步两个服务。仅在两个服务已经在运行非基于GTID的复制时,才需要这个步骤。对于全新的服务,可以直接跳到第三步。使两台服务均变成只读模式,在两台机器上分别执行如下语句:
mysql> SET @@GLOBAL.read_only = ON;
等待所有正在执行的事务提交,或者回滚。使得从服务器追赶上主服务器,这个非常重要,在继续接下来的步骤前,一定要确保这个点。
如果您将二进制日志用于除复制以外的任何操作,例如执行时间点备份和恢复,请等到不需要这些旧二进制日志时再执行。理想情况下,等待服务器清除所有二进制日志,并等待任何现有备份过期。
注意:包含没有gtid的事务的日志不能在启用gtid的服务器上使用。在继续之前,您必须确保没有gtid的事务在拓扑中的任何地方都不存在。
第二步,停止两个服务。
--未完待续--