全局锁 Flush tables with read lock 被简称为 FTWRL。全局锁也可以理解为数据库级别的锁,这把锁会加给整个数据库。
FTWRL(flush tables with read lock)命令主要被备份工具使用,使用逻辑方式进行备份(mydumper,mysqldump)或物理方式进行备份(percona-xtrabackup),为了保证数据的一致性,这两种备份方式都会在备份过程中执行 flush table with read lock 这个命令,通过执行FTWRL,来对事务和非事务表来加table level级别的共享锁,取得此时的gtid或者binlog偏移量,继而得到某一个时间点的一致性备份数据(数据与binlog位点匹配)。
命令如下:
Flush tables with read lock;
执行这个语句,它会让整个库被处于只读状态。之后的增删改语句就会被阻塞,包括事务的提交,表结构的修改与创建等。
FTWRL 这个命令主要用于备份工具获取一致性备份(数据与binlog位点匹配)。由于 FTWRL 总共需要持有两把全局的 MDL 锁(上全局读锁和上全局 COMMIT 锁),并且还需要关闭所有表对象,因此这个命令的杀伤性很大,执行命令时容易导致库 hang 住。
如果是主库,则业务无法正常访问;如果是备库,则会导致 SQL 线程卡住,主备延迟。
FTWRL 到底做了哪些事情?主要干 3 件事。
1. 上全局读锁(lock_global_read_lock);
2. 清理表缓存(close_cached_tables);
3. 上全局COMMIT锁(make_global_read_lock_block_commit)。
解读如下:
1. 第一步的作用是堵塞更新**😗*上全局读锁会导致所有更新操作都会被堵塞;关闭表过程中,如果有大查询导致关闭表等待,那么所有访问这个表的查询和更新都需要等待;对于innodb引擎而言,其自身的MVCC机制,可以保证读到老版本数据,因此第一步对它使多余的。
2. **第二步清理表缓存****作用:**清理表缓存也就是每个表在内存中都有一个 table_cache,不同表的 cache 对象通过 hash 链表维护,刷新它自己的 data,同时也刷新操作系统的 data 到 disk 上;这个操作对于myisam有意义,关闭myisam表时,会强制要求表的缓存落盘,这对于物理备份myisam表是有意义的,因为物理备份是直接拷贝物理文件。对于innodb表,则无需这样,因为innodb有自己的redolog,只要记录当时LSN,然后备份LSN以后的redolog即可
3. **第三部上全局COMMIT锁作用:**上全局读锁和上全局 COMMIT 锁都是通过 MDL 锁实现的。上全局 COMMIT 锁时,会堵塞活跃事务提交;主要是保证能获取一致性的binlog位点,这点对于myisam和innodb作用是一样的。
小结:
FTWRL对于innodb引擎而言,最重要的是获取一致性位点,前面两个步骤是可有可无;因此如果业务表全部是innodb表,这把大锁从原理上来讲是可以拆的。
此外,官方版本的5.5和5.6对于mysqldump做了一个优化,主要改动是,5.5备份一个表,锁一个表,备份下一个表时,再上锁一个表,已经备份完的表锁不释放,这样持续进行,直到备份完成才统一释放锁。5.6则是备份完一个表,就释放一个锁,实现主要是通过innodb的保存点机制。
使用场景**:**全库做逻辑备份
1. 在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
2. 在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。
3. 全局读锁期间只能读,不能更新是个问题。
官方自带的逻辑备份工具是 mysqldump。
当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
此时存在的一个疑惑:有了这个功能(使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图),为什么还需要 FTWRL 呢?
回答:
一致性读是好,但前提是引擎要支持这个隔离级别。比如,对于 MyISAM 这种不支持事务的引擎,如果备份过程中有更新,总是只能取到最新的数据,那么就破坏了备份的一致性。这时,我们就需要使用 FTWRL 命令了。所以,single-transaction 方法只适用于所有的表使用事务引擎的库。
如果有的表使用了不支持事务的引擎,那么备份就只能通过 FTWRL 方法,这往往是 DBA 要求业务开发人员使用 InnoDB 替代 MyISAM 的原因之一;
这也是为什么建议大家使用MySQL8.0的一个原因,因为MySQL8.0 的元数据存储引擎不再使用MyISAM ,元数据管理支持InnoDB存储引擎。
接下来再具体看mysqldump需要增加FTWRL的参数场景:
1. 设置了master-data
2. 设置了singal-transaction和flush-logs
percona版在如下情况需要增加FTWRL:
1. 设置了singal-transaction和flush-logs
具体看下mysqldump代码如下(代码版本8.0.21);
下面是从FTWRL倒UNLOCK的过程:
if ((opt\_lock\_all\_tables || opt\_master\_data || //如果设置了 master data 设置flush table with read lock
(opt\_single\_transaction && flush\_logs)) &&//如果设置了single transaction和flush logs 设置flush table with read lock
do\_flush\_tables\_read\_lock(mysql)) //设置flush table with read lock
goto err;
/\*
/\*
Flush logs before starting transaction since
this causes implicit commit starting mysql-5.5.
\*/
if (opt\_lock\_all\_tables || opt\_master\_data ||
(opt\_single\_transaction && flush\_logs) || opt\_delete\_master\_logs) {
if (flush\_logs || opt\_delete\_master\_logs) {//如果设置了 flush logs 进行日志刷新
if (mysql\_refresh(mysql, REFRESH\_LOG)) { //进行日志刷新
DB\_error(mysql, "when doing refresh");
goto err;
}
verbose\_msg("-- main : logs flushed successfully!\\n");
}
/\* Not anymore! That would not be sensible. \*/
flush\_logs = false;
}
if (opt\_delete\_master\_logs) {
if (get\_bin\_log\_name(mysql, bin\_log\_name, sizeof(bin\_log\_name))) goto err;
}
if (opt\_single\_transaction && start\_transaction(mysql)) goto err; //开启事务 RR
/\* Add 'STOP SLAVE to beginning of dump \*/
if (opt\_slave\_apply && add\_stop\_slave()) goto err;
/\* Process opt\_set\_gtid\_purged and add SET @@GLOBAL.GTID\_PURGED if required.
\*/
if (process\_set\_gtid\_purged(mysql)) goto err; //设置GTID,如果设置了gtid\_purged 这个函数会跳过
if (opt\_master\_data && do\_show\_master\_status(mysql)) goto err; //获取主库binlog位置
if (opt\_slave\_data && do\_show\_slave\_status(mysql)) goto err; //slave\_data 设置相关 从show slave中获取
if (opt\_single\_transaction &&
do\_unlock\_tables(mysql)) /\* unlock but no commit! \*/
goto err;
percona中增加了判断函数 check_consistent_binlog_pos,如下:
if (opt\_single\_transaction && opt\_master\_data)
{
/\*
See if we can avoid FLUSH TABLES WITH READ LOCK with Binlog\_snapshot\_\*
variables.
\*/
consistent\_binlog\_pos= check\_consistent\_binlog\_pos(NULL, NULL);
}
if ((opt\_lock\_all\_tables || (opt\_master\_data && !consistent\_binlog\_pos) ||//consistent\_binlog\_pos 0 需要 1 不需要
(opt\_single\_transaction && flush\_logs)))
{
if (do\_flush\_tables\_read\_lock(mysql))
goto err;
}
如何规避FTWRL?
1. master-data 一般备份都会增加,因此只能在低峰期进行备份,尽量减少影响。
2. 考虑关闭参数 slave_preserve_commit_order。但是FTWRL的堵塞还是存在,只是不会产生死锁。
3. 如果主从同步压力不大,可以考虑关闭MTS(多线程复制,5.7版本后,建议最少升级到5.7.19版本+,修复很多Bug)。但是FTWRL的堵塞还是存在,只是不会产生死锁。
4. 建议升级到MySQL8.0,元数据存储引擎使用innoDB,不再使用MyISAM存储引擎,在使用mysqldump逻辑备份时就可以只使用–single-transaction,不用再添加–meta-data参数。
【参考】
https://blog.csdn.net/andong154564667/article/details/82117727
【参考】
https://blog.csdn.net/weixin_30080745/article/details/113305346
【参考】
https://www.jianshu.com/p/b141585cd844
【参考】
https://blog.csdn.net/weixin_42139042/article/details/113907281
【参考】
https://www.cndba.cn/cndba/dave/article/4681
【参考】
https://www.cndba.cn/cndba/dave/article/4680
欢迎关注个人微信公众号