锁粒度越小并发越强但开销越大;innodb行锁生效依赖索引命中,否则升级为表级或间隙锁;需用performance_schema.data_locks查实时锁状态,避免缺失索引导致锁升级。

锁粒度直接影响并发吞吐量
MySQL 的锁粒度越小,并发能力越强,但管理开销越大;粒度越大,锁冲突概率升高,容易阻塞。InnoDB 默认行级锁,但实际是否生效取决于查询是否命中索引——WHERE 条件没走索引时,会升级为表级锁(或间隙锁/临键锁导致的伪“扩大锁定”)。
- 全表扫描更新:
UPDATE users SET status=1;→ 锁全表(即使引擎是 InnoDB) - 主键精确更新:
UPDATE users SET status=1 WHERE id=123;→ 只锁对应行 - 范围查询更新:
UPDATE users SET status=1 WHERE created_at > '2024-01-01';→ 可能锁住索引范围内的所有行 + 间隙,阻塞插入
如何判断当前 SQL 锁了哪些行
别靠猜。用 INFORMATION_SCHEMA.INNODB_TRX 和 INFORMATION_SCHEMA.INNODB_LOCKS(MySQL 5.7)或 performance_schema.data_locks(MySQL 8.0+)查实时锁状态。最常用的是:
SELECT * FROM performance_schema.data_locks\G
重点关注 LOCK_DATA 字段(显示被锁的具体值或范围)、LOCK_MODE(如 RECORD 行锁、RECORD,GAP 间隙锁、TABLE 表锁)。
- 事务长时间未提交,
data_locks中持续存在记录 → 锁未释放 -
LOCK_MODE出现INSERT_INTENTION→ 其他事务正尝试插入,被间隙锁阻塞 -
LOCK_DATA显示NULL或超大范围 → 可能因缺失索引导致锁升级
避免锁升级的关键实操点
锁升级不是显式命令,而是优化器在无法精确定位时的退化行为。核心是让每条 DML 都能通过索引快速定位到最小数据集。
- UPDATE/DELETE 必须带 SARGable 条件:避免
WHERE YEAR(created_at) = 2024,改用WHERE created_at >= '2024-01-01' AND created_at - 复合索引顺序要匹配查询条件:如常查
WHERE status=1 AND category='A',索引应建为(status, category),而非反过来 - 批量更新分页做:用
LIMIT控制每次只改 1000 行,配合WHERE id > last_id ORDER BY id LIMIT 1000避免长事务 - 显式事务中,DML 尽量靠近
COMMIT,不要在事务里混杂慢查询、远程调用等无关操作
READ-COMMITTED 和 REPEATABLE-READ 对锁的影响
隔离级别不只影响可见性,直接决定锁类型和持续时间。默认 REPEATABLE-READ 下,普通 SELECT 不加锁,但 UPDATE/DELETE 会用临键锁(Next-Key Lock),既锁记录又锁间隙;而 READ-COMMITTED 下只锁命中的行,不锁间隙(除非显式加 SELECT ... FOR UPDATE)。
- 高并发写入场景(如秒杀扣库存),可考虑将业务表设为
READ-COMMITTED,减少间隙锁冲突 - 但需确认业务能接受 RC 级别的不可重复读——比如两次
SELECT同一订单状态可能不同 - 修改级别后,务必测试
UPDATE ... WHERE ... IN (subquery)类语句,RC 下子查询可能被多次执行,带来性能回退
锁的“看不见成本”往往藏在等待链里:一个慢查询持锁 2 秒,可能让 50 个后续请求排队。与其事后分析锁等待,不如在写 SQL 时就问一句:这条语句,到底需要锁几行?











