聚合查询被阻塞主因是where条件未走索引导致全表扫描,在rr级别下加共享锁和间隙锁,与并发写事务冲突;需为status建索引,避免函数操作,优化group by/order by索引设计,并警惕长事务中“只读”查询的锁持有时间。

聚合查询本身不加写锁,但高并发下卡住,大概率是因为它被其他事务的写操作锁住了——不是它在等别人释放锁,是别人正在等它释放锁,或者它正踩在别人加锁的路径上。
SELECT COUNT(*) FROM t WHERE status = 'pending' 为什么会被阻塞?
这不是聚合的问题,是WHERE条件没走索引导致全表扫描。InnoDB在RR隔离级别下,对扫描到的每一行(包括不存在的间隙)都可能加共享锁(S lock)或间隙锁(gap lock)。如果此时另一个事务正在UPDATE同一张表的某几行并持有X锁,聚合查询就会在那些行上卡住,状态变成LOCK WAIT。
- 用
EXPLAIN FORMAT=TRADITIONAL确认key字段是否命中索引;若显示type = ALL或Extra含Using where但无Using index,基本就是全表扫 -
status字段必须有索引,单列索引即可;复合索引如(status, created_at)也有效,但WHERE created_at > ?单独用就无效 - 避免在WHERE里用函数:
WHERE UPPER(status) = 'PENDING'会让索引失效
GROUP BY + ORDER BY 的聚合为什么更容易锁等待?
这类查询常触发临时表和文件排序(Using temporary; Using filesort),而临时表构建过程会延长持有MDL锁(metadata lock)的时间,同时扫描范围扩大,锁住更多间隙。更危险的是:如果ORDER BY字段没索引,MySQL可能先扫全表再排序,锁住所有匹配行,且加锁顺序不可控。
- 确保
GROUP BY和ORDER BY字段都在同一个复合索引里,且满足最左前缀,例如(status, user_id, created_at)支持GROUP BY status, user_id ORDER BY created_at - 不要依赖
ORDER BY RAND()做分页聚合,它强制全表扫描+临时表,高并发下极易成为瓶颈 - 如果只是要总数,优先用
SELECT COUNT(*)而非SELECT COUNT(DISTINCT x),后者无法走覆盖索引,大概率触发回表+临时表
为什么加了索引还是锁等待?
索引只是让扫描变快,不代表不加锁。尤其在RR隔离级别下,即使WHERE status = 'pending'走了索引,只要结果集不止一行,InnoDB仍会对每行加S锁,并对相邻间隙加gap lock——这时如果有其他事务正INSERT或UPDATE这些间隙附近的记录,就会互相等待。
- 检查
information_schema.INNODB_TRX中长时间运行的事务,特别是TRX_STATE = 'RUNNING'但TRX_STARTED很早的,它们可能是“隐形锁源” - 用
SELECT * FROM performance_schema.data_locks(MySQL 8.0+)看当前谁持有什么锁;注意LOCK_DATA字段显示具体行或间隙值 - 如果业务允许,把隔离级别降到
READ COMMITTED,能避免gap lock,但需确认业务能否接受幻读
真正难处理的不是锁本身,而是聚合查询常被当成“只读安全操作”放进长事务里——比如先查待处理数,再根据结果决定是否发通知。这种模式会让S锁持有时间远超必要,把整个表的写入都拖慢。别信“只读就没问题”,要看它读了多久、读了哪些位置。











