能力有限,有误请谅解,仅为浅析,仅供参考。
最近遇到多次堵塞备份的问题,也一直都8.0 xtrbackup备份的方式不太了解,因此做了一下学习。我们知道8.0过后加入了新的备份锁,主要包含下面2个部分:
- lock instance for backup(WL#9451 Backup Lock)
- ps.log_status (WL#9452: Log Position Lock)
这里先从以往的文章中看一下FTWRL的原理和其堵塞的场景,然后集中分析一下新的备份锁大体的实现方式。
一、FTWRL的原理
实际上这部分我们可以在函数mysql_execute_command寻找case SQLCOM_FLUSH 的部分,实际上主要调用函数为reload_acl_and_cache,其中核心部分为:
if (thd->global_read_lock.lock_global_read_lock(thd))//加 MDL GLOBAL 级别S锁
return 1; // Killed
if (close_cached_tables(thd, tables, //关闭表操作释放 share 和 cache
((options & REFRESH_FAST) ? FALSE : TRUE),
thd->variables.lock_wait_timeout)) //等待时间受lock_wait_timeout影响
{
/*
NOTE: my_error() has been already called by reopen_tables() within
close_cached_tables().
*/
result= 1;
}
if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // MDL COMMIT 锁
{
/* Don't leave things in a half-locked state */
thd->global_read_lock.unlock_global_read_lock(thd);
return 1;
}
更具体的关闭表的操作和释放table缓存的部分包含在函数close_cached_tables中,我就不详细写了。但是我们需要明白table缓存实际上包含两个部分:
- table cache define:每一个表第一次打开的时候都会建立一个静态的表定义结构内存,当多个会话同时访问同一个表的时候,从这里拷贝成相应的instance供会话自己使用。由参数table_definition_cache定义大小,由状态值Open_table_definitions查看当前使用的个数。对应函数get_table_share。
- table cache instance:同上所述,这是会话实际使用的表定义结构是一个instance。由参数table_open_cache定义大小,由状态值Open_tables查看当前使用的个数。对应函数open_table_from_share。
这里我统称为table缓存,好了下面是我总结的FTWRl的大概步骤:
第一步: 加MDL LOCK类型为GLOBAL 级别为S。如果出现等待状态为‘Waiting for global read lock’。注意select语句不会上GLOBAL级别上锁,但是DML/DDL/FOR UPDATE语句会上GLOBAL级别的IX锁,IX锁和S锁不兼容会出现这种等待。下面是这个兼容矩阵:
| Type of active |
Request | scoped lock |
type | IS(*) IX S X |
---------+------------------+
IS | + + + + |
IX | + + - - |
S | + - + - |
X | + - - - |
第二步:推进全局表缓存版本。源码中就是一个全局变量 refresh_version++。
第三步:释放没有使用的table 缓存。可自行参考函数close_cached_tables函数。
第四步:判断是否有正在占用的table缓存,如果有则等待,等待占用者释放。等待状态为'Waiting for table flush'。这一步会去判断table缓存的版本和全局表缓存版本是否匹配,如果不匹配则等待如下:
for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); //寻找整个 table cache shared hash结构
if (share->has_old_version()) //如果版本 和 当前 的 refresh_version 版本不一致
{
found= TRUE;
break; //跳出第一层查找 是否有老版本 存在
}
}
...
if (found)//如果找到老版本,需要等待
{
/*
The method below temporarily unlocks LOCK_open and frees
share's memory.
*/
if (share->wait_for_old_version(thd, &abstime,
MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
{
mysql_mutex_unlock(&LOCK_open);
result= TRUE;
goto err_with_reopen;
}
}
而等待的结束就是占用的table缓存的占用者释放,这个释放操作存在于函数close_thread_table中,如下:
if (table->s->has_old_version() || table->needs_reopen() ||
table_def_shutdown_in_progress)
{
tc->remove_table(table);//关闭 table cache instance
mysql_mutex_lock(&LOCK_open);
intern_close_table(table);//去掉 table cache define
mysql_mutex_unlock(&LOCK_open);
}
最终会调用函数MDL_wait::set_status将FTWRL唤醒,也就是说对于正在占用的table缓存释放者不是FTWRL会话而是占用者自己。不管怎么样最终整个table缓存将会被清空,如果经过FTWRL后去查看Open_table_definitions和Open_tables将会发现重新计数了。下面是唤醒函数的代码,也很明显:
bool MDL_wait::set_status(enum_wait_status status_arg) open_table
{
bool was_occupied= TRUE;
mysql_mutex_lock(&m_LOCK_wait_status);
if (m_wait_status == EMPTY)
{
was_occupied= FALSE;
m_wait_status= status_arg;
mysql_cond_signal(&m_COND_wait_status);//唤醒
}
mysql_mutex_unlock(&m_LOCK_wait_status);//解锁
return was_occupied;
}
第五步:加MDL LOCK类型COMMIT 级别为S。如果出现等待状态为‘Waiting for commit lock’。如果有大事务的提交很可能出现这种等待。
二、FTWRL堵塞和被堵塞场景
(1)被什么堵塞
- 长时间的DDL\DML\FOR UPDATE堵塞FTWRL,因为FTWRL需要获取 GLOBAL的S锁,而这些语句都会对GLOBAL持有IX(MDL_INTENTION_EXCLUSIVE)锁,根据兼容矩阵不兼容。等待为:Waiting for global read lock 。本文的案例1就是这种情况。
- 长时间的select堵塞FTWRL, 因为FTWRL会释放所有空闲的table缓存,如果有占用者占用某些table缓存,则会等待占用者自己释放这些table缓存。等待为:Waiting for table flush 。本文的案例2就是这种情况,会堵塞随后关于本表的任何语句,即便KILL FTWRL会话也不行,除非KILL掉长时间的select操作才行。实际上flush table也会存在这种堵塞情况。
- 长时间的commit(如大事务提交)也会堵塞FTWRL,因为FTWRL需要获取COMMIT的S锁,而commit语句会对commit持有IX(MDL_INTENTION_EXCLUSIVE)锁,根据兼容矩阵不兼容。
(2)堵塞什么
- FTWRL会堵塞DDL\DML\FOR UPDATE操作,堵塞点为 GLOBAL级别 的S锁,等待为:Waiting for global read lock 。
- FTWRL会堵塞commit操作,堵塞点为COMMIT的S锁,等待为Waiting for commit lock 。
- FTWRL不会堵塞select操作,因为select不会在GLOBAL级别上锁。
最后提醒一下很多备份工具都要执行FTWRL操作,包含mysqldump和5.7的XTRBACKUP ,一定要注意它的堵塞/被堵塞场景和特殊场景,当然xtrbackup 8.0有所改善,我们后面进行分析
三、慢查询对FTWRL的记录方式
通常来讲DML(SELECT FOR UPDATE)和SELECT都会堵塞FTWRL,具体参见上面,而且我们知道这都是MDL LOCK堵塞,在以往的慢查询认知中,MDL LOCK堵塞是计入到慢查询的LOCK time,但是FTWRL却不一样。因为FTWRL不会过接口mysql_lock_tables,因此MDL LOCK堵塞时间不会被慢查询记录,正常的语句关于慢查询的部分大概如下:
1、语句开始, 记录开始时间点为 X
2、获取MDL LOCK -> 正常语句如果本处超时(超过lock_wait_timeout)直接退出,也不会记录到下面一步记录的lock time时间中,因此lock time为0。
3、上MySQL层锁(比如myisam,记录lock time时间差量(1-3的时间差量Y)
4、优化器优化语句。
5、执行器开始执行语句,与innodb层交互, 如果每行需要等待innodb行锁,记录每次获取的lock time时间差量(第5步的每次等待innodb行锁的时间差量综合为Y)
6、语句结束,触发记录慢查询接口,并且获取当前时间点为(Z)判定记录慢查询如下。
如果(Z-X)-Y > 慢查询设置的时间则需要记录
也就是慢查询 query time:Z-X lock time为:Y
需要注意的是语句异常结束也会记录慢查询。
但是FTWRL不记录Y这个时间,因此慢查询中对于FTWRL 统计的方式变为了(Z-X),也就是它执行了多久就是多久,且lock time为0。*
超时(lock_wait_timeout设置为120)
# Time: 2022-05-04T16:26:35.226862+08:00
# User@Host: root[root] @ localhost [] Id: 10534
# Query_time: 120.004726 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0
SET timestamp=1651652675;
flush table with read lock;
kill FTWRL
# Time: 2022-05-04T16:27:22.215598+08:00
# User@Host: root[root] @ localhost [] Id: 10534
# Query_time: 30.727657 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0
SET timestamp=1651652811;
flush table with read lock;
上面两个例子表明了这一点,我们注意到Lock_time为0。还需要注意的是如果正常语句遇到lock_wait_timeout超时记录的慢查询的Lock_time也是0,原因一致(不过接口mysql_lock_tables),如下:
# Time: 2022-05-06T11:06:29.665024Z
# User@Host: root[root] @ localhost [] Id: 257
# Schema: test Last_errno: 1205 Killed: 0
# Query_time: 120.004564 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0 Rows_affected: 0
# Bytes_sent: 67
SET timestamp=1651835189;
select * from testup11;
可以看到这是一个select由于Waiting for table flush等待超时,但是语句Lock_time为0,执行时间大约为120秒,也就是超时时间(lock_wait_timeout设置为120)。
四、lock instance for backup
这个操作主要堵塞的是DDL操作,包含不限于如下一些常见的操作:
CREATE_TABLE、CREATE_INDEX、ALTER_TABLE、TRUNCATE、DROP_TABLE、LOAD、CREATE_DB
、DROP_DB、ALTER_DB、RENAME_TABLE、DROP_INDEX、CREATE_VIEW、DROP_VIEW、CREATE_TRIGGER、DROP_TRIGGER、CREATE_EVENT、ALTER_EVENT、DROP_EVENT、IMPORT、RENAME_USER、DROP_USER、ALTER_USER、GRANT、REVOKE、GRANT_ROLE、REVOKE_ROLE、DROP_ROLE、CREATE_ROLE、OPTIMIZE、CREATE_FUNCTION、CREATE_PROCEDURE、DROP_PROCEDUR、DROP_FUNCTION、ALTER_PROCEDURE、ALTER_FUNCTION、REPAIR、ANALYZE、ALTER_TABLESPACE
我也随意测试了一些操作,确实都会被堵塞。从内部来看,实际上这个操作的功效依然是通过MDL LOCK的实现的,其策略为m_scoped_lock_strategy,那么从scoped策略的兼容性来看,如下:
对于lock instance for backup本生而言实际上是做了的如下操作:
Sql_cmd_lock_instance::execute
->acquire_exclusive_backup_lock
->acquire_mdl_for_backup
获取的MDL LOCK为 MDL_key::BACKUP_LOCK+MDL_SHARED(S)
并且看起来 MDL_key::BACKUP_LOCK的锁类型只会是MDL_SHARED(S)或者MDL_INTENTION_EXCLUSIVE(IX),因为acquire_mdl_for_backup里有断言:
DBUG_ASSERT(mdl_type == MDL_SHARED || mdl_type == MDL_INTENTION_EXCLUSIVE);
对于上面提到的会被堵塞的这些操作,则是在响应的地方加入了获取 MDL_key::BACKUP_LOCK+MDL_INTENTION_EXCLUSIVE(IX)
根据兼容矩阵,S和IX并不兼容,但是IX和IX之间是兼容的,因此只要不执行lock instance for backup操作,则不会有任何影响。而对于用户而言会发现processlist出现Waiting for backup lock字样则为这样的堵塞。测试如下:
mysql> show processlist;
+----+------+-----------+---------+---------+------+-------------------------+-------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+---------+---------+------+-------------------------+-------------------+
| 11 | root | localhost | testpri | Query | 4 | Waiting for backup lock | OPTIMIZE table t1 |
| 12 | root | localhost | t10 | Sleep | 175 | | NULL |
| 13 | root | localhost | testpri | Query | 0 | init | show processlist |
当然既然是MDL LOCK,它同样受到参数lock_wait_timeout参数的影响。比如我这里的操作为OPTIMIZE如下:
mysql> OPTIMIZE table t1;
+------------+----------+----------+--------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+------------+----------+----------+--------------------------------------------------------+
| testpri.t1 | optimize | Error | Lock wait timeout exceeded; try restarting transaction |
| testpri.t1 | optimize | status | Operation failed |
+------------+----------+----------+--------------------------------------------------------+
2 rows in set (2 min 0.01 sec)
需要的注意的这个锁并不会堵塞任何DML和SELECT操作(即便是mysiam表也不会堵塞DML操作),但是保护了元数据的正确性。如果要获取的一致的数据我们就需要额外的机制也就是下面谈到的ps.log_status。
五、ps.log_status 访问的方式
我们先看看它包含了哪些信息:
*************************** 1. row ***************************
SERVER_UUID: b69a6373-3a03-11ec-add6-000c29948de2
LOCAL: {"gtid_executed": "b69a6373-3a03-11ec-add6-000c29948de2:1-73,\nd179f8f4-728f-11eb-b96e-000c2956fa4f:1-42", "binary_log_file": "mysql-bin.000016", "binary_log_position": 236}
REPLICATION: {"channels": [{"channel_name": "group_replication_applier", "relay_log_file": "relay-bin-group_replication_applier.000066", "relay_log_position": 152}, {"channel_name": "group_replication_recovery", "relay_log_file": "relay-bin-group_replication_recovery.000008", "relay_log_position": 156}]}
STORAGE_ENGINES: {"InnoDB": {"LSN": 7109536475, "LSN_checkpoint": 7109536475}}
1 row in set (8 min 14.10 sec)
其中最重要的就是GTID信息,binlog的pos,这个实在我们恢复后需要的,当然我们看到还包含了主从的通道relay log信息,以及Innodb层的当前LSN信息和CHECKPOINT LSN信息。在完成这个功能上看,几乎是复用了现有的mutex,步骤总共有好几步,下面是重要的一些步骤:
- 加入了一个Log_resource类,那么将GTID信息/binlog信息/relay log信息/LSN信息 都虚拟成一种资源。
- 新增了多个继承类继承于Log_resource类,也就是上面的提到的分别为,并且他们都完成了Log_resource父类的虚函数,如下
- Log_resource_mi_wrapper (relay log信息)
-> Log_resource_mi_wrapper::lock
-> Log_resource_mi_wrapper::unlock
-> Log_resource_mi_wrapper::collect_info - Log_resource_binlog_wrapper(binlog信息)
-> Log_resource_binlog_wrapper::lock
-> Log_resource_binlog_wrapper::unlock
-> Log_resource_binlog_wrapper::collect_info - Log_resource_gtid_state_wrapper(GTID信息)
-> Log_resource_gtid_state_wrapper::lock
-> Log_resource_gtid_state_wrapper::unlock
-> Log_resource_gtid_state_wrapper::collect_info - Log_resource_hton_wrapper(引擎层信息)
->Log_resource_hton_wrapper::lock
->Log_resource_hton_wrapper::unlock
->Log_resource_hton_wrapper::collect_info
这里可以看到还是很有调理的。
- 新增一个mutex LOCK_collect_instance_log 用于防止并发的访问ps.log_status信息,同时新增一个访问接口table_log_status::make_row,如果在这个函数下面,就能清楚的访问的步骤大概为
- 加LOCK_collect_instance_log 锁
- 将所有资源,也就是上面4个资源放到一个叫做resources的list容器
- 依次调用每个资源的***::lock,先加锁,避免产生不一致的数据
- 依次拿到需要的信息,这里因为前面各个资源都加锁了,因此获取的信息是一致的。
- 依次调用每个资源的***::unlock,解锁。
- 解开LOCK_collect_instance_log 锁
这样我们就可以发现,获取所有的信息的时候,实际上它们是在所有信息都加锁的情况下拿到了,是一致的,全部获取完成后才开始解锁。
- innodb引擎层实现lock/unlock/collect_info,对应了如下的调用
-> innobase_collect_hton_log_info
-> innobase_unlock_hton_log
-> innobase_lock_hton_log
实际上就是来获取Innodb层的当前LSN信息和CHECKPOINT LSN信息。那么下面我们简单分析一下具体是如何做的,这里抛开relay log的信息,这貌似不太重要。
六、ps.log_status 重要信息获取的代价
- binlog位点信息
小于8.0.27,获取位点的信息主要加锁为LOCK_log,这是flush\fsync\commit,三阶段中第一阶段上的锁,通过加这个锁能够保证堵塞提交,获取到稳定的binlog位点。
binlog_cache_data::flush
...
->MYSQL_BIN_LOG::Binlog_ofile::write(这里增加binlog的位点信息)
- GTID信息
获取GTID信息主要是在flush\fsync\commit,三阶段中第三阶段上的锁,这个时候主要防止有GTID新加入到gtid_executed,这样就能获得稳定的GTID信息了,需要注意的GTID的生成是在flush阶段,但是加入到gtid_executed是在commit阶段。而加锁的锁为 global_sid_lock,对于加入到gtid_executed会上此锁。
MYSQL_BIN_LOG::process_commit_stage_queue
->Gtid_state::update_commit_group (加global_sid_lock)
->Gtid_state::update_gtids_impl_own_gtid (加入gtid_execute)
- Innodb 层的信息
这个信息包含2个如下:
- 当前LSN信息:这个信息和show engine中Log sequence number 一致,主要是在MTR提交的时候增加的LSN,用于判断是否有足够的log buffer空间
->mtr_t::commit
->mtr_t::Command::execute
->log_buffer_reserve
->log_buffer_s_lock_enter_reserve
加锁为log_buffer_x_lock_enter,而在MTR提交的时候需要log_buffer_s_lock_enter_reserve,这两个函数都比较复杂涉及的东西也比较多,没有仔细学习。但是可以清楚的是堵塞了MTR的提交操作,也就是写入不能写到log_buffer,这样来保证了当前LSN信息的稳定。
- CHECKPOINT LSN:本信息和show engine中的Last checkpoint at一致的,加锁同CKPT线程使用的是同一个也就是log_t::checkpointer_mutex(log_checkpointer_mutex_enter(log)),那么保证了CKPT LSN信息的稳定。
我们这里看到实际上访问ps.log_status 不是完全没有代价的,只是相对于FTWRL来讲,通过细化达到了较小的代价。至少这里看有如下代价:
- Innodb层不能产生新的redo,这会堵塞数据的修改操作。
- MySQL层不能提交任何事务,这会堵塞所有正在提交的事务。
相对于FTWRL,table share不会涉及到释放和加载,因此select是不会受到影响的。如果不涉及到大事务,获取log_status的操作应该是比较快的,因为大事务的提交实在是有点慢。如果在flush阶段需要较多的时间将binlog从临时文件拷贝到binlog,这个时间就需要持有LOCK_log锁。
七、一个非常重要的BUG。
bug如下:
https://bugs.mysql.com/bug.php?id=102175
注意这个BUG同样导致xtrbackup出现问题。如下:
https://jira.percona.com/browse/PS-7873
本BUG导致的是访问log_status的时候发现GTID信息落后于binlog信息,我觉得本BUG足以让数据恢复出现故障,非常重要。
我们使用bug描述中的案例,测试者加了一个sleep(1)在Gtid_state::update_commit_group获取global_sid_lock之后,拖长GTID信息加入到gtid_executed的过程,很容易就重现了。如下:
mysql> select LOCAL from performance_schema.log_status\G
*************************** 1. row ***************************
LOCAL: {"gtid_executed": "0f9b691a-612b-11eb-bbf5-74d83e29c093:1-31", "binary_log_file": "binlog.000001", "binary_log_position": 1932788}
1 row in set (3.29 sec)
SHOW BINLOG EVENTS;
| binlog.000001 | 1932313 | Gtid | 1 | 1932392 | SET @@SESSION.GTID_NEXT= '0f9b691a-612b-11eb-bbf5-74d83e29c093:54'|
| binlog.000001 | 1932392 | Query | 1 | 1932467 | BEGIN|
| binlog.000001 | 1932467 | Table_map | 1 | 1932532 | table_id: 106 (test.sbtest1)|
| binlog.000001 | 1932532 | Write_rows | 1 | 1932757 | table_id: 106 flags: STMT_END_F|
| binlog.000001 | 1932757 | Xid | 1 | 1932788 | COMMIT /* xid=62 */
这里我们发现GTID信息为1-31,binlog信息为1932788,但是通过解析binlog发现,binlog位点1932788的gtid为54。很显然这是有问题,如果这个时候我们用这个gtid信息去做主从那么肯定是失败的。
那么这个BUG如何出现的呢?实际上通过前文的描述我们来画一个简单的示意图如下:
但是如图,比如当前处理到了1-10这个GTID的pos,然后解锁LOCK_log,然后log_status拿到了,而对于commit阶段处理的信息可能还在1-6这个GTID范围,一旦加入了gtid_execute,global_sid_lock就释放了,那么log_status也拿到了global_sid_lock锁,接着开始获取GTID信息和binlog信息,那么这个时候获取的信息GTID信息是1-6,binlog信息是1-10。
接着官方在8.0.27修复了这个问题,加大了锁的粒度,这个红色框框都加锁了,也就是加大了加锁的访问包含了LOCK_sync /LOCK_commit,这样一来整个提交操作三阶段都会被包裹,同时global_sid_lock的获取也在其中,只要有事务正在这个流程中就必须堵塞log_status信息的获取,当然也就解决了这个问题。但是这也稍微加大了log_status访问的代价,因为我们知道fsync实际上也是比较耗时的一个步骤,特别是IO压力较高的情况下,但是不管这么说都比FTWRL的影响小了很多。
八、xtrbackup 8.0.28和新的备份锁
这里测试使用的是xtrbkacup8.0.28+MySQL 8.0.28,其次需要注意的是mysqldump有就是FTWRL。
原则上xtrbackup使用上面的提到的新的备份锁,但是这有一个前提就是不能有myisam的表,当然默认情况下8.0不会有myisam的表,但是是可以人为建立的,如果有myisam的表最后依旧会使用了FTWRL,这个可以开general log来看,稍微看看语句的输出部分,就会发现xtrbackup一开始就在做检测如下:
char *count_str =
read_mysql_one_value(mysql_connection,
"SELECT COUNT(*) FROM information_schema.tables "
"WHERE engine = 'MyISAM' OR engine = 'RocksDB'");
unsigned long long count = strtoull(count_str, nullptr, 10);
have_unsafe_ddl_tables = (count > 0); //如果存在mysiam表记录标记
包含myisam表的general log重要输出:
2022-05-08T21:18:36.546024+08:00 31 Query SELECT COUNT(*) FROM information_schema.tables WHERE engine = 'MyISAM' OR engine = 'RocksDB'
2022-05-08T21:18:36.547809+08:00 31 Query LOCK INSTANCE FOR BACKUP
2022-05-08T21:18:36.550288+08:00 31 Query SELECT CONCAT(table_schema, '/', table_name), engine FROM information_schema.tables WHERE engine NOT IN ('MyISAM', 'InnoDB', 'CSV',
'MRG_MYISAM', 'ROCKSDB') AND table_schema NOT IN ( 'performance_schema', 'information_schema', 'mysql')
2022-05-08T21:18:47.049757+08:00 31 Query FLUSH NO_WRITE_TO_BINLOG TABLES
2022-05-08T21:18:47.052098+08:00 31 Query FLUSH TABLES WITH READ LOCK
2022-05-08T21:18:47.254181+08:00 31 Query FLUSH NO_WRITE_TO_BINLOG BINARY LOGS
2022-05-08T21:18:47.258193+08:00 31 Query SELECT server_uuid, local, replication, storage_engines FROM performance_schema.log_status
2022-05-08T21:18:47.264598+08:00 31 Query FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS
2022-05-08T21:18:48.268716+08:00 31 Query UNLOCK INSTANCE
2022-05-08T21:18:48.268877+08:00 31 Query UNLOCK TABLES
我们从原理上其实也能理解,如果有myisam的表不管是lock instance for backup还是ps.log_status都是无法堵塞DML的,因为ps.log_status只是获取一个一致性的点而已,但是备份myisam表的这段时间的redo是没有,他们是不可能达到一致的。当然没有myisam表的情况就不会有FTWRL,这样就使用到了我们这里分析的新的备份锁,可以自己开一下general log看看。
同时8.0.27之前xtrbakcup使用ps.log_status就可能触发我们上面说的BUG,这是需要注意的,我们如果恢复的GTID和xtrbackup 记录在文件中的GTID有出入以恢复后的GTID为准,这点是和5.7完全不一样的,5.7恢复后的GTID因为受到gtid_executed表切换更新的影响,而初启动的时候始化又依赖gtid_executed表,所以可能大大落后于文件中的GTID,我们需要以xtrbackup的文件为准。
以上。。。