本文目录导读:
锁等待是数据库高并发场景下的常见性能瓶颈,要优化并缩短锁等待时间,核心思路是 减少锁的持有时间 和 减少锁的冲突概率。
以下是系统性的优化策略,分为 SQL与索引层面、事务与隔离级别、架构与表设计、监控与排查 四个维度:
SQL与索引层面(收益最大,最常用)
- 创建高效索引(最核心):
- 原因:没有索引会导致表锁(MyISAM)或行锁升级为页锁/间隙锁(InnoDB),且扫描大量无效行占用锁资源。
- 做法:为
WHERE、JOIN、ORDER BY、GROUP BY涉及的字段建立合适的复合索引,确保能通过索引快速定位到少数目标行。
- 缩小锁定范围:
- 写法:避免使用
SELECT *,只选择必要的列,使用LIMIT限制更新/删除的行数(分批处理)。 - 条件:保证
UPDATE和DELETE的WHERE条件能精准使用索引,避免全表扫描锁定无关行。
- 写法:避免使用
- 避免锁升级:
- 原因:少量行锁可能升级为表锁(如行数占比过高)。
- 做法:大表的大批量更新/删除务必拆分成多批次(如每次
LIMIT 1000)执行,中间加上sleep(0.02)或等待。
事务与隔离级别(减少锁持有时间)
- 缩短事务长度(最直接):
- 原则:事务里只放最精简的 DML(数据操纵语言),不要在事务中执行耗时操作(如远程 RPC(远程过程调用)、复杂计算、大量数据导出)。
- 例子:如果必须查询后更新,尽量把 只读查询 放在事务外,事务内只执行
SELECT ... FOR UPDATE+UPDATE。
- 降低隔离级别(权衡一致性):
- 关系:
可重复读(Repeatable Read,RR)会产生间隙锁(Next-Key Lock),读已提交(Read Committed,RC)只存在行锁。 - 建议:如果业务不要求可重复读,建议使用 读已提交(RC) 级别(如 MySQL 默认是 RR,可考虑改为 RC),能大幅减少锁冲突。
- 关系:
- 使用乐观锁(代替悲观锁):
- 场景:读多写少、冲突概率低的场景。
- 做法:表中加
version字段,UPDATE table SET value='x', version=version+1 WHERE id=y AND version=old_version,如果更新失败,重试即可,避免了SELECT ... FOR UPDATE的长时间阻塞。
架构与表设计(从根源减少冲突)
- 热点数据拆分:
- 问题:某一行(如爆款商品的库存)被大量并发操作锁住。
- 方案:将单一热点行拆分为多行(如库存按
id%100分散到100个库存子行),更新时随机选择一行,最后汇总。
- 分库分表:
- 作用:将锁冲突分散到不同的物理库/表中,从根本上降低单点的锁竞争。
- 使用排队机制:
- 场景:极高并发写入(如秒杀)。
- 方案:将请求放入 Redis 队列或消息队列(MQ),由单线程或小池子异步写入数据库,化并行请求为串行请求。
监控与排查(定位问题根因)
- 查看当前锁等待:
- MySQL:
SHOW PROCESSLIST查找Waiting for table level lock或Lock wait timeout exceeded;SELECT * FROM performance_schema.data_lock_waits;或SHOW ENGINE INNODB STATUS\G查看具体谁锁了谁。 - PostgreSQL:
SELECT * FROM pg_locks WHERE NOT granted;结合pg_stat_activity找到阻塞源。
- MySQL:
- 分析死锁日志:
- 开启数据库死锁日志(如
innodb_print_all_deadlocks),分析锁冲突路径,优化 SQL 执行顺序(保证所有事务按同一顺序访问资源)。
- 开启数据库死锁日志(如
- 设置合理的超时时间:
innodb_lock_wait_timeout设为较短的值(如 5-10秒),避免因为一个慢事务拖垮整个应用。
总结优化优先级
- 立即检查:当前等待的 SQL 是否有 索引?是否 锁住了太多行?
- 事务检视:事务里是否夹杂了 慢查询 或 外部调用?
- 隔离级别:能否从 RR 降到 RC?
- 拆分化繁:热点行能否 拆分?大批量更新能否 分批?
一个简单的排查案例:
如果你的系统出现 Lock wait timeout exceeded; try restarting transaction 错误,通常是:
- 一个长事务(A)持有了某行的锁,但事务没提交。
- 另一个事务(B)要更新同一行,等待超时。
解决:
- 找到事务A对应的代码,确认其是否在事务内执行了不必要的耗时操作。
- 确保事务A及时
COMMIT或ROLLBACK。 - 为事务A的
UPDATE语句添加索引,减少其锁定的行数,从而让事务B可能命中不同行。
标签: 优化缩短