查清是否真在等锁,需先看SHOW PROCESSLIST中State是否为Updating或Waiting for table metadata lock;若为Sending data等则非锁问题,再结合performance_schema.events_statements_summary_by_digest定位真实耗时SQL。

查清是不是真在等锁,而不是慢查询
高并发写入时看到大量 SQL 执行时间变长,第一反应不该是“SQL 写得差”,而是确认它卡在哪:是执行本身慢,还是被锁住动不了。直接看 SHOW PROCESSLIST,重点盯 State 列——如果一堆线程停在 Updating、Waiting for table metadata lock 或 Locked,基本就是锁争用;若全是 Sending data 或 Copying to tmp table,那可能是临时表或计算瓶颈。
更准的方式是查 performance_schema.events_statements_summary_by_digest,按 SUM_TIMER_WAIT 排序,找真实耗时高的 digest。注意:慢日志里“慢”的语句,可能只是排队等了 2 秒才轮到执行,实际执行只要 5ms。
- 检查
long_query_time是否设得太低(比如 0.1),把等待时间也记成“慢查询” - 用
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED看有没有长事务没提交——这是最常被忽略的锁源 - 确认是否有 DDL 正在运行(
ALTER TABLE等),它会持元数据锁,阻塞所有同表 DML
定位行锁升级为表锁的隐性路径
InnoDB 本该用行锁,但一旦 UPDATE/DELETE 不走索引,就会全表扫描并升级为表级锁——此时所有对该表的写操作都会排队。这不是 bug,是引擎行为。
验证方法很简单:对可疑语句加 EXPLAIN,看 type 是不是 ALL,key 是不是 NULL;再查 SHOW STATUS LIKE 'Table_locks_waited',如果这个值每秒涨 > 1,说明表锁争用已成常态。
- 常见诱因:WHERE 条件字段没索引、用了函数(如
WHERE DATE(created_at) = '2026-09-01')、隐式类型转换(如字符串字段传数字) - MyISAM 表混在库中?哪怕只有一张,它的任何写操作都是表锁,且会阻塞 InnoDB 表的元数据操作
-
innodb_table_locks = ON(默认)时,InnoDB 也会响应LOCK TABLES,别让应用层误发
抓出高并发写入下的内部资源争用点
即使所有 SQL 都走索引、无全表扫描,5.7 的写入瓶颈仍可能来自 InnoDB 内部结构竞争,尤其是 buf_pool_mutex 和 btr_search_latch。现象是 CPU 单核跑满,其他核空闲,SHOW ENGINE INNODB STATUS\G 的 SEMAPHORES 段里反复出现 buf0buf.cc 或 btr0sea.cc。
- 先查
SELECT @@innodb_buffer_pool_instances,如果是 1,立刻改——设为 8 或与 CPU 核数对齐,但必须保证innodb_buffer_pool_size是innodb_buffer_pool_chunk_size × instances的整数倍,否则启动失败或 OOM - 如果
adaptive_hash_index = ON(默认),且大量写入集中在同一二级索引(如status字段),AHI latch 就会成热点;可临时关掉:SET GLOBAL innodb_adaptive_hash_index = OFF(5.7.20+ 支持) -
innodb_thread_concurrency设为 0(默认)通常没问题,但如果观察到大量线程卡在srv_conc_enter_innodb,可尝试设为 CPU 核数 × 2
确认磁盘临时表是否正在制造元数据锁风暴
写入类查询本身不常建临时表,但带 GROUP BY、ORDER BY、DISTINCT 的聚合更新(比如统计后更新汇总字段)会触发。一旦内存临时表超限落盘,默认用 MyISAM 建磁盘临时表——而 MyISAM 是表级锁,一个大 GROUP BY 正在写磁盘临时表,其他所有想用临时表的查询都得等它。
查 SHOW STATUS LIKE 'Created_tmp%',重点关注 Created_tmp_disk_tables 每秒增长量。再执行 SELECT @@internal_tmp_mem_storage_engine,若返回 MEMORY(5.7 默认),且你有大量排序/分组语句,基本就中招了。
- 根本解法不是调大
tmp_table_size,而是给GROUP BY或ORDER BY字段加联合索引,让排序走索引,避免临时表 - 确保
tmp_table_size和max_heap_table_size设为相同值(如 64M),防止因取小值而“意外”落盘 - 禁用查询缓存(
query_cache_type = 0),QC 在某些聚合场景下会强制触发临时表逻辑
最容易被绕过的点是:磁盘临时表的引擎选择不可控——MySQL 5.7 落盘时默认用 MyISAM,除非你显式配了 innodb_tmpdir 并重启。而 MyISAM 表级锁 + 元数据锁持有时间长,会让问题从“单个慢查询”扩散成“全库写入阻塞”。这点在压测时很难复现,却在真实大促流量下突然爆发。


















