mysql中的锁粒度与性能优化的关系

1次阅读

innodb 行锁会退化为表锁:当无索引字段+查询条件时触发全表扫描并加表级x锁;间隙锁在rr级别下阻塞插入,rc级别不启用;真实锁等待需通过show engine innodb status分析transactions和lock wait段落。

mysql中的锁粒度与性能优化的关系

表级锁 vs 行级锁:什么时候会退化成表锁

mysql 的锁粒度直接影响并发能力,但实际中 InnoDB 并非总能用上行级锁。比如执行 UPDATE t SET a=1 WHERE b LIKE '%abc%' 时,若 b 列没有索引,优化器无法定位具体行,就会对全表加 X 锁(即表级写锁)。这种退化在慢查询日志里常表现为 Rows_examined 远大于 Rows_affected

  • 有主键或唯一索引的等值条件(WHERE id = 123)→ 精确行锁
  • 范围查询(WHERE created_at > '2024-01-01')→ 可能锁住间隙(Gap Lock),甚至整个索引段
  • 无索引字段 + 条件 → 全表扫描 + 表级锁(即使引擎是 InnoDB)
  • select ... for UPDATE 在 RC 隔离级别下不加 Gap Lock,但在 RR 下会,影响并发插入

间隙锁(Gap Lock)为何让“插入变慢”

间隙锁是 InnoDB 实现可重复读(RR)的关键机制,但它会锁住索引中不存在的“空档”。例如 id 是主键,当前有记录 (1),(5),(10),执行 SELECT * FROM t WHERE id BETWEEN 3 AND 7 FOR UPDATE,InnoDB 会锁住 (1,5) 和 (5,10) 这两个间隙 —— 此时 INSERT INTO t VALUES (4) 就会被阻塞。

  • 间隙锁只存在于 RR 隔离级别;RC 下不启用,但幻读风险上升
  • 唯一索引的等值查询(含主键)不会触发间隙锁,只锁匹配行
  • 非唯一索引的等值查询仍可能锁间隙(如 name='alice',而 name 不唯一)
  • SELECT ... LOCK IN SHARE MODE 同样受间隙锁影响,不只是 FOR UPDATE

如何用 SHOW ENGINE INNODB STATUS 查真实锁等待

仅看 PROCESSLIST 或慢日志不够,真正卡在哪、谁在等谁,得靠 InnoDB 自带的状态快照。执行后重点关注 TRANSACTIONSLOCK WAIT 段落。

SHOW ENGINE INNODB STATUSG
  • ---TRANSACTION [hex_id], ACTIVE [sec] sec 行,确认事务是否长时间未提交
  • 看到 lock_mode X locks rec but not gap waiting → 正在等某一行锁释放
  • 看到 lock_mode X locks gap before rec insert intention waiting → 被间隙锁堵住插入
  • Trx has been waiting [n] sec for this lock 超过 5 秒就该警惕了
  • 注意 mysql tables in use [n], locked [m] 中 locked > 1 可能是隐式锁升级

降低锁冲突的实操建议

锁不是越细越好,而是要和业务节奏对齐。盲目拆分事务或加索引反而引入新问题。

  • 写操作尽量走主键或覆盖索引,避免回表 → 减少锁行数
  • 批量更新改用 INSERT ... ON DUPLICATE KEY UPDATE 替代先查后更,减少事务持有时间
  • 高并发计数场景(如点赞数)别直接 UPDATE t SET cnt = cnt + 1,改用 redis 计数+异步落库
  • 长事务必须拆:比如导出报表,不要在一个事务里查 10 万行再统一更新,分页处理并及时提交
  • 避免在事务中调用外部 http 接口或 sleep(),锁会一直持有着

锁粒度和性能之间没有银弹,关键在理解每一行 SQL 落到索引树上的真实路径 —— 看执行计划只是起点,看锁状态才是真相。

text=ZqhQzanResources