doc: 阅读replication文档

This commit is contained in:
wu xiangkai
2025-10-21 15:09:37 +08:00
parent bca627e036
commit 4cdefcd2bb

View File

@@ -5,6 +5,16 @@
- [GTID Format and Storage](#gtid-format-and-storage)
- [GTID Sets](#gtid-sets)
- [msyql.gtid\_executed](#msyqlgtid_executed)
- [mysql.gtid\_executed table compression](#mysqlgtid_executed-table-compression)
- [GTID生命周期](#gtid生命周期)
- [What changes are assign a GTID](#what-changes-are-assign-a-gtid)
- [Replication Implementation](#replication-implementation)
- [Replication Formats](#replication-formats)
- [使用statement-based和row-based replication的优缺点对比](#使用statement-based和row-based-replication的优缺点对比)
- [Advantages of statement-based replication](#advantages-of-statement-based-replication)
- [Disadvantages of statement-based replication](#disadvantages-of-statement-based-replication)
- [Advantages of row-based replication](#advantages-of-row-based-replication)
- [disadvantages of row-based replication](#disadvantages-of-row-based-replication)
# Replication
Replication允许数据从一个database server被复制到一个或多个mysql database serversreplicas。replication默认是异步的replicas并无需永久连接即可接收来自source的更新。基于configuration可以针对所有的databases、选定的databases、甚至database中指定的tables进行replicate。
@@ -56,6 +66,8 @@ GTID的分配区分了client trasactions在source上提交的事务以及r
Replicated transaction将会使用`事务被source server分配的GTID`。在replicated transaction开始执行前GTID就已经存在并且即使replicated transaction没有写入到replica的binary log中或是被replica过滤该GTID也会被持久化。`mysql.gtid_executed` system table将会被用于保存`应用到给定server的所有事务的GTID但是已经存储到当前active binary log file的除外`
> `active binary log file`中的gtid最终也会被写入到`gtid_executed`表中但是对于不同的存储引擎写入时机可能不同。对于innodb而言在事务提交时就会写入gtid_executed表而对于其他存储引擎而言则会在binary log发生rotation/server shutdown后才会将active binary log中尚未写入的GTIDs写入到`mysql.gtid_executed`表
GTIDs的auto-skip功能代表一个在source上提交的事务最多只能在replica上应用一次这有助于保证一致性。一旦拥有给定GTID的事务在给定server上被提交任何后续执行的事务如果拥有相同的GTID都会被server忽略。并不会抛出异常并且事务中的statements都不会被实际执行。
如果拥有给定GTID的事务在server上已经开始执行但是尚未提交或回滚那么任何尝试开启一个`拥有相同GTID事务的操作都会被阻塞`。该server并不会开始执行拥有相同GTID的事务也不会将控制权交还给client。一旦第一个事务提交或回滚那么被阻塞的事务就可以开始执行。如果第一次执行被回滚那么被阻塞事务可以实际执行如果第一个事务成功提交那么被阻塞事务并不会被实际执行而是会被自动跳过。
@@ -167,6 +179,145 @@ CREATE TABLE gtid_executed (
只有当`gtid_mode`被设置为`ON``ON_PERMISSIVE`GTIDs才会被存储到mysql.gtid_executed中。如果binary logging被禁用或者`log_replica_updates`被禁用server在事务提交时将会把属于个各事务的GTID和事务一起存储到buffer中并且background threads将会周期性将buffer中的内容以entries的形式添加到`mysql.gtid_executed`表中。
对于innodb存储引擎而言如果binary logging被启用server更新`mysql.gtid_executed`表的方式将会和`binary logging或replica update logging被启用`时一样都会在每个事务提交时存储GTID。
对于innodb存储引擎而言如果binary logging被启用server更新`mysql.gtid_executed`表的方式将会和`binary logging或replica update logging被启用`时一样都会在每个事务提交时存储GTID。对于其他存储引擎则是在binary log rotation发生时或server shut down时更新`mysql.gtid_executed`表的内容。
如果mysql.gtid_executed表无法被写访问并且binary log file因`reaching the maximum file size`之外的任何理由被rotated那么current binary log file将仍被使用。并且当client发起rotation时将会返回错误信息并且server将会输出warning日志。如果`mysql.gtid_executed`无法被写访问并且binary log单个文件大小达到`max_binlog_size`那么server将会根据`binlog_error_action`设置来执行操作。如果`IGNORE_ERROR`被设置那么server将会输出error到日志并且binary logging将会被停止如果`ABORT_SERVER`被设置那么server将会shutdown。
> 因为写入到active binary log的GTIDs最终也要被写入`mysql.gtid_executed`表,但是该表若当前不可写访问,那么此时将无法触发`binary log rotation`。
>
> MySQL.gtid_executed中缺失的GTIDs必须包含在active binary log file中如果log file触发rotation但是无法向mysql.gtid_executed中写入数据那么rotation是不被允许的。
##### mysql.gtid_executed table compression
随着时间的推移mysql.gtid_executed表会填满大量行行数据代表独立的GTID示例如下
```
+--------------------------------------+----------------+--------------+----------+
| source_uuid | interval_start | interval_end | gtid_tag |
|--------------------------------------+----------------+--------------|----------+
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 31 | 31 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 32 | 32 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 33 | 33 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 34 | 34 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 35 | 35 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 36 | 36 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 37 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38 | 38 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39 | 39 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 40 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41 | 41 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42 | 42 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43 | 43 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 44 | 44 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 45 | 45 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 46 | 46 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 47 | 47 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 48 | 48 | Domain_1 |
```
为了节省空间可以对gtid_executed表周期性的进行压缩将多个`single GTID`替换为单个`GTID set`,压缩后的数据如下;
```
+--------------------------------------+----------------+--------------+----------+
| source_uuid | interval_start | interval_end | gtid_tag |
|--------------------------------------+----------------+--------------|----------+
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 31 | 35 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 36 | 39 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 43 | Domain_1 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 44 | 46 | Domain_2 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 47 | 48 | Domain_1 |
...
```
server可通过名为`thread/sql/compress_gtid_table`的前台线程来执行gtid_executed表的压缩操作该线程并不会在`show processlist`的输出中被列出,但是可以通过查询`threads`表来查看,示例如下:
```sql
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
```
当server启用了binary log时上述压缩方式并不会被使用`mysql.gtid_executed`在每次binary log rotation时会被压缩。但是当binary logging被禁用时`thread/sql/compress_gtid`线程处于sleep状态每有一定数量的事务被执行时线程会wake up并执行压缩操作。在table被压缩之前经过的事务数量即压缩率可由system variable `gtid_executed_compression_period`来进行控制。如果将值设置为0代表该thread永远不会wake up。
相比于其他存储引擎innodb事务写入`mysql.gtid_executed`的过程有所不同在innodb存储引擎中过程通过线程`innodb/clone_gtid_thread`来执行。此GTID持久化线程将会分组收集GTIDs并将其刷新到`mysql.gtid_executed`表中并对table进行压缩。如果server中既包含innodb事务又包含non-innodb事务那么由`compress_gtid_table`线程执行的压缩操作将会干扰`clone_gtid_thread`的工作,并对其效率进行显著下降。为此,在此场景下更推荐将`gtid_executed_compression_period`设置为0故而`compress_gtid_table`线程将永远不被激活。
`gtid_executed_compression_period`的默认值为0且所有事务不论其所属存储引擎都会被`clone_gitid_thread`写入到`mysql.gtid_exec uted`中。
当server实例启动后如果`gtid_executed_compression_preiod`被设置为非0值并且`compress_gtid_table`线程也启动在多数server配置中将会对`msyql.gtid_executed`表执行显式压缩。压缩通过线程的启动触发。
#### GTID生命周期
GTID生命周期由如下步骤组成
1. 事务在source上被执行并提交client transaction将会被分配GTIDGTID由`source UUID``smallest nonzero transaction sequence number not yet used on this server`组成。GTID也会被写入到binary log中在log中GTID被写入在事务之前。如果client transaction没有被写入binary log例如事务被过滤或事务为read-only那么该事务不会被分配GTID
2. 如果为事务分配了GTID那么在事务提交时GTID会被原子的持久化。在binary log中GTID会被写入到事务开始的位置。不管何时如果binary log发生rotation或server shutdownserver会将所有写入到binary log file中事务的GTIDs写入到`mysql.gtid_executed`表中
3. 如果为事务分配了GTID那么GTID的外部化是`non-atomically`在事务提交后非常短的时间。外部化过程会将GTID添加到`gtid_executed` system variable所代表的GTID set中`@@GLOBAL.gtid_executed`。该GTID set中包含`representation of the set of all committed GTID transactions`并且在replication中会作为代表server state的token使用。
1. 当binary logging启用时`gtid_executed` system variable代表的GTIDs set是已被应用事务的完整记录。但是`mysql.gtid_executed`表则并记录所有GTIDs可能由部分GTIDs仍存在于active binary log file中尚未被写入到gtid_executed table
4. 在binary log data被转移到replica并存储在replica的relay log中后replica会读取GTID的值并且将其设置到`gtid_next` system variable中。这告知replica下一个事务将会使用`gtid_next`所代表的GTID。replica在session context中设置`gtid_next`
5. replica在处理事务前会验证没有其他线程已经持有了`gtid_next`中GTID的所有权。通过该方法replica可以保证该该GTID关联的事务在replica上没有被应用过并且还能保证没有其他session已经读取该GTID但是尚未提交关联事务。故而如果并发的尝试应用相同的事务那么server只会让其中的一个运行。
1. replica的`gtid_owned` system variable`@@GLOBAL.gtid_owned`代表了当前正在使用的每个GTID和拥有该GTID的线程ID。如果GTID已经被使用过并不会抛出错误`auto-skip`功能会忽略该事务
6. 如果GTID尚未被使用那么replica将会对replicated transaction进行应用。因为`gtid_next`被设置为了`由source分配的GTID`replica将不会尝试为事务分配新的GTID而是直接使用存储在`gtid_next`中存储的GTID
7. 如果replica上启用了binary loggingGTID将会在提交时被原子的持久化写入到binary log中事务开始的位置。无论何时如果binary log发生rotation或server shutdownserver会将`先前写入到binary log中事务的GTIDs`写入到`mysql.gtid_executed`表中
8. 如果replica禁用binary logGTID将会原子的被持久化直接被写入到`mysql.gtid_executed`表中。mysql会向事务中追加一个statement并且将GTID插入到table中该操作是原子的在该场景下`mysql.gtid_executed`表记录了完整的`transactions applied on the replica`
9. 在replicated transaction提交后很短的时间GTID会被非原子的externalizedGTID会被添加到replica的`gtid_executed` system variable代表的GTIDs中。在source中`gtid_executed` system variable包含了所有提交的GTID事务。
在source上被完全过滤的client transactions并不会被分配GTID因此其不会被添加到`gtid_executed` system variable或被添加到`mysql.gtid_executed`表中。然而replicated transaction中的GTIDs即使在replica被完全过滤也会被持久化。
- 如果binary logging在replica开启被过滤的事务将会作为gtid_log_event的形式被写入到binary log后续跟着一个empty transaction事务中仅包含BEGIN和COMMIT语句
- 如果binary logging在replica被禁用给过滤事务的GTID将会被写入到`mysql.gtid_executed`
将filtered-out transactions的GTIDs保留可以确保`mysql.gtid_executed`表和`gtid_executed`system variable中的GTIDs可以被压缩。同时其也能确保replica重新连接到source时filtered-out transactions不会被重新获取
在多线程的replica上`replica_parallel_workers > 0`事务可以被并行的应用故而replicated transactions可以以不同的顺序来提交除非`replica_preserve_commit_order = 1`)。在并行提交时,`gtid_executed`system variable中的GTIDs将包含多个GTID ranges多个范围之间存在空隙。在多线程replicas上只有最近被应用的事务间才会存在空隙并且会随着replication的进行而被填充。当replication threads通过`STOP REPLICA`停止时这些空隙会被填补。当发生shutdown event时server failure导致此时replication 停止,空隙仍可能保留。
##### What changes are assign a GTID
GTID生成的典型场景为`server为提交事务生成新的GTID`。然而GTIDs可以被分配给除事务外的其他修改且在某些场景下一个事务可以被分配多个GTIDs。
每个写入binary log的数据库修改(`DDL/DML`)都会被分配一个GTID。其包含了自动提交的变更、使用`BEGIN...COMMIT`的变更和使用`START TRANSACTION`的变更。一个GTID会被分配给数据库的`creation, alteration, deletion`操作,并且`non-table` database object例如`procedure, function, trigger, event, view, user, role, grant``creation, alteration, deletion`也会被分配GTID。
非事务更新和事务更新都会被分配`GTID`额外的对于非事务更新如果在尝试写入binary log cahche时发生disk write failure导致binary log中出现间隙那么生成的日志事件将会被分配一个GTID。
## Replication Implementation
在replication的设计中source server会追踪其binary log中所有对databases的修改。binary log中记录了自server启动时所有修改数据库结构或内容的事件。通常`SELECT`语句将不会被记录,其并没有对数据库结构或内容造成修改。
每个连接到source的replica都会请求binary log的副本replica会从source处拉取数据而不是source向replica推送数据。replica也会执行其从binary log收到的事件。`发生在source上的修改将会在replica上进行重现`。在重现过程中,会发生表创建、表结构修改、数据的新增/删除/修改等操作。
由于每个replica都是独立的每个连接到source的replica其对`source中binary log`内容的replaying都是独立的。此外因为每个replica只通过请求source来接收binary log的拷贝replica能够以其自身的节奏来读取和更新其数据副本并且其能在不对source和其他replicas造成影响的条件下开启或停止replication过程。
### Replication Formats
在binary log中events根据其事件类型以不同的格式被记录。replication使用的foramts取决于事件被记录到source server的binary log中时所使用的foramt。binary log format和replciation过程中使用到的format其关联关系如下
- 当使用statement-based binary logging时source会将sql statements写入到binary log。从source到replica的replication将会在replica上执行该sql语句。这被称之为statement-based replication关联了mysql statement-based binary logging format
- 当使用row-based loggging时source将会将`table rows如何变更`的事件写入到binary log中。在replica中将会重现events对table rows所做的修改。则会被成为row-based replication
- `row-based logging`是默认的方法
- 可以配置mysql使用`mix-format logging`,但使用`mix-format logging`将默认使用statement-based log。但是对于特定的语句也取决于使用的存储引擎在某些床惊吓log也会被自动切换到row-based。使用mixed format的replication被称之为mix-based replication或mix-format replication
mysql server中的logging format通过`binlog_format` system variable来进行控制。该变量可以在session/global级别进行设置
- 在将variable设置为session级别时将仅会对当前session生效并且仅持续到当前session结束
- 将variable设置为global级别时该设置将会对`clients that connect after the change`生效,`但对于any current client sessions包括.发出该修改请求的session都不生效`
#### 使用statement-based和row-based replication的优缺点对比
每个binary logging format都有优缺点。对大多数usersmixed replication format能够提供性能和数据完整性的最佳组合。
##### Advantages of statement-based replication
- 在使用statement-based格式时会向log files中写入更少的数据。特别是在更新或删除大量行时使用statement-based格式能够导致更少的空间占用。在从备份中获取和恢复时其速度也更快
- log files包含所有造成修改的statements可以被用于审核database
##### Disadvantages of statement-based replication
- 对于Statement-based replication而言statements并不是安全的。并非所有对数据造成修改的statements都可以使用statement-based replication。在使用statement-based replication时任何非确定性的行为都难以复制例如在语句中包含随机函数等非确定性行为
- 对于复杂语句statement必须在实际对目标行执行修改前重新计算。而当使用row-based replication时replica可以直接修改受影响行而无需重新计算
##### Advantages of row-based replication
- 所有的修改可以被replicated其是最安全的replication形式
##### disadvantages of row-based replication
- 相比于statement-based replicationrow-based replication通常会向log中ieur更多数据特别是当statement操作大量行数据是
- 在replica并无法查看从source接收并执行的statements。可以通过`mysqlbinlog``--base64-output=DECODE-ROWS``--verbose`选项来查看数据变更