flush tables with read lock卡住写操作的根本原因在于其三步串行执行机制,尤其是第二步close_cached_tables需等待所有表引用释放,遇长查询或僵尸连接即阻塞,导致后续dml/ddl堆积在waiting for table flush状态。

FLUSH TABLES WITH READ LOCK 为什么会卡住后续所有写操作
它不是简单地“加一把读锁”,而是分三步串行执行,每一步都可能成为阻塞点。真正卡住业务的,往往不是锁本身,而是第二步“清理表缓存”时遇到正在运行的长查询。
核心流程如下:
-
lock_global_read_lock:获取全局读锁(MDL 锁),此时新来的 DML/DDL 已开始排队等待 -
close_cached_tables:尝试关闭所有打开的表对象——这一步会遍历table_def_cache,对每个还在被使用的表(share->ref_count > 0),调用MDL_wait::timed_wait等待其释放;如果有慢查询正拿着某张表的引用,这里就卡住 -
make_global_read_lock_block_commit:再加一把全局 COMMIT 锁,确保活跃事务无法提交
注意:前两步都依赖 LOCK_open 互斥量,整个过程是串行的。只要其中任意一步没完成,后续所有写请求都会堆积在 Waiting for table flush 状态。
为什么 SHOW PROCESSLIST 里一堆线程卡在 Waiting for table flush
这不是“锁没生效”,恰恰是 FTWRL 正在努力生效的表现。这个状态说明:FTWRL 已拿到第一步锁,但卡在第二步关闭表的过程中,而其他线程的写操作,因为拿不到表的 MDL 写锁,只能等 FTWRL 完全执行完才能继续。
常见诱因包括:
- 某个会话正在执行未结束的
SELECT(尤其是扫描大表、没走索引的慢查询) - 有连接长期空闲但未关闭,仍持有某些表的打开句柄
- 应用使用连接池且未配置
testOnBorrow,导致僵尸连接残留表引用
你可以用这条语句快速定位谁在挡路:
SELECT ID, USER, HOST, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE STATE = 'Sending data' OR STATE = 'Copying to tmp table' OR STATE LIKE '%Locked%';
FTWRL 执行期间,已开启的事务还能不能提交
不能。第三步 make_global_read_lock_block_commit 就是专干这事的——它会阻塞所有活跃事务的 COMMIT,哪怕事务早已执行完 UPDATE,也得等到 UNLOCK TABLES 或会话断开后才能落盘。
这意味着:
- 你看到
SHOW ENGINE INNODB STATUS里还有TRX_STATE = RUNNING的事务,它们其实已经卡在提交阶段 - 主从复制中,从库 SQL 线程也会被卡住,表现为
Seconds_Behind_Master持续上涨 - 如果这时 kill 掉 FTWRL 所在会话,锁立刻释放,但那些卡在
COMMIT的事务会一起冲出去,可能造成备份不一致
实际运维中最容易忽略的阻塞细节
很多人只盯着“有没有长事务”,却忘了 FTWRL 对“表打开状态”的强依赖。即使没有活跃事务,只要还有连接打开着某张表(哪怕只是刚 SELECT 了一行),close_cached_tables 就得等它释放引用。
所以真正要排查的不是 INNODB_TRX,而是:
- 查
information_schema.INNODB_TRX看长事务(常规动作) - 查
information_schema.PROCESSLIST看非 Sleep 状态的连接(重点看Sending data、Sorting result、Creating sort index) - 查
performance_schema.table_handles(MySQL 5.7+)确认哪些表还被持有句柄 - 特别注意监控、备份、健康检查类脚本——它们常带
AUTOCOMMIT=0连接,开个SELECT就不关,静默阻塞 FTWRL
FTWRL 的阻塞逻辑藏在表生命周期管理底层,不是靠“事务是否活跃”能完全覆盖的。一旦卡住,光杀事务没用,得先清掉所有表级引用。











