group by在只读副本上引发锁等待,根本原因在于read_only未真正生效(缺super_read_only=on),导致select for update或临时表操作与sql thread回放冲突;同时无索引group by触发全表扫描、filesort及gap lock,在repeatable read下加剧锁竞争。

GROUP BY 本身不写数据,但在只读副本上引发锁等待,说明它根本不是“纯只读”——背后有隐式写操作或隔离级别冲突。核心原因是:你连的所谓“只读副本”,实际没启用 read_only = ON,或者被其他连接干扰了事务可见性状态。
read_only 没真开,SQL Thread 和你的查询在抢同一张表
MySQL 从库的“只读”是靠配置项 read_only 实现的,但它默认不阻止 SUPER 权限用户写入。很多运维漏配 super_read_only = ON,导致报表脚本、监控工具、甚至开发误连从库执行 SELECT ... FOR UPDATE 或 INSERT INTO tmp_table,直接和 SQL Thread 回放主库变更形成锁竞争。
-
read_only = ON只限制普通用户,SUPER 用户仍可写 -
super_read_only = ON才真正禁止所有写入(包括 SQL Thread 之外的任何连接) - 如果
GROUP BY查询里用了临时表(比如GROUP BY字段无索引、结果集大、或含TEXT列),MySQL 会自动创建内部临时表,而临时表在磁盘上时可能触发innodb_file_per_table = OFF下的共享表空间争用 - 更隐蔽的是:某些 ORM 或中间件会自动加
START TRANSACTION,哪怕你只发一条SELECT,也会让事务持有 MVCC 快照,间接延长锁生命周期
GROUP BY 触发隐式排序或临时表,放大锁持有窗口
InnoDB 在执行 GROUP BY 时,若无法利用索引完成分组(例如字段没索引、或索引顺序不匹配 GROUP BY 列顺序),就会走 filesort + 内部临时表。这个过程虽不修改业务表,但:
- 需要扫描全表或大范围索引,期间可能碰到 SQL Thread 正在更新的行,触发
RECORD LOCK等待 - 若隔离级别是
REPEATABLE READ(默认),扫描过程中还会加GAP LOCK,阻塞其他连接插入新行——哪怕你只是查,别人写也会卡 - 大结果集的
GROUP BY可能触发tmp_table_size/max_heap_table_size限制,强制落盘到innodb_temp_data_file_path,而该路径若在系统表空间,会加剧页级争用
示例:
SELECT product_id, COUNT(*) FROM orders WHERE created_at > '2026-10-01' GROUP BY product_id;
如果 created_at 没索引,或 product_id 索引未覆盖查询条件,就大概率走全表扫描+临时表。
错把 SHOW SLAVE STATUS 的 Seconds_Behind_Master 当成健康指标
Seconds_Behind_Master = 0 不代表从库“此刻无锁压力”。它只反映 SQL Thread 最后一个事件的时间戳差,不体现当前是否有长事务持锁、是否正在回放大事务、是否刚被阻塞过又恢复。
- 一个
GROUP BY查询跑了 8 秒,期间 SQL Thread 尝试更新同一张orders表的某行,但那行正被该查询的事务锁着(因开启了事务或用了FOR UPDATE)→ 直接触发错误 1205 - 日志里看不到明显慢 SQL,因为阻塞源是“读事务持有锁”,不是“写 SQL 慢”
-
SHOW PROCESSLIST里看到GROUP BY状态是Sending data,容易误判为网络或磁盘问题,其实它正在等锁
关键点在于:锁等待从来不管你是 SELECT 还是 UPDATE,只看谁先拿到锁、谁在等、事务有没有提交。线上从库一旦允许非只读连接存在,GROUP BY 就可能变成锁冲突的导火索——尤其当它扫的表正好是主库高频更新的核心业务表时。










