在高并发业务场景中,mysql的锁竞争是引发慢查询的常见原因之一,不合理的索引设计会进一步放大锁冲突的影响,导致业务接口响应延迟升高。理解mysql的锁机制与索引的关联,是优化这类问题的核心。

mysql常见锁类型与锁竞争产生原因
mysql的锁按照粒度可以分为全局锁、表级锁和行级锁,日常业务中引发慢查询的多为行级锁竞争,常见的行级锁包括记录锁、间隙锁、临键锁。锁竞争通常发生在多个事务同时操作同一批数据,且加锁顺序不一致或者加锁范围过大的场景。
常见锁类型说明
| 锁类型 | 作用范围 | 常见触发场景 |
|---|---|---|
| 记录锁 | 锁定单条索引记录 | 通过唯一索引精确匹配更新数据 |
| 间隙锁 | 锁定索引记录之间的间隙 | 范围查询且事务隔离级别为可重复读 |
| 临键锁 | 记录锁+间隙锁的组合 | 范围查询且使用非唯一索引 |
索引不合理如何加剧锁竞争
索引设计缺陷是导致锁范围扩大的核心原因,常见的问题包括未使用索引导致锁升级、索引区分度低导致锁范围过大、索引顺序不一致导致死锁等。
无索引导致的表级锁问题
如果更新语句的where条件没有命中索引,mysql会进行全表扫描,此时会对所有扫描到的行加锁,相当于表级锁,会阻塞所有对该表的写操作。例如以下语句,如果user_name字段没有索引:
-- user_name无索引时,该语句会锁全表 UPDATE user_info SET age = 20 WHERE user_name = '张三';
索引区分度低导致锁范围扩大
如果where条件使用的索引区分度很低,比如用性别字段做索引,那么更新时会锁定大量符合条件的行,其他事务操作这些行的数据就会被阻塞。例如status字段只有0和1两个值,区分度极低:
-- status索引区分度低,会锁定大量行 UPDATE order_info SET status = 1 WHERE status = 0 AND create_time < '2024-01-01';
锁竞争慢查询的排查步骤
遇到疑似锁竞争导致的慢查询时,可以按照以下步骤排查:
- 查看慢查询日志,确认慢查询的SQL语句和执行时间
- 使用
SHOW ENGINE INNODB STATUS命令查看最近的死锁和锁等待信息 - 分析慢查询SQL的执行计划,确认是否命中索引,索引区分度如何
- 查看当前数据库的锁等待情况,使用
SELECT * FROM information_schema.INNODB_LOCKS;和SELECT * FROM information_schema.INNODB_LOCK_WAITS;语句
优化方案
索引优化
首先确保慢查询的where条件、连接条件都命中合适的索引,优先选择区分度高的字段作为索引,避免索引失效的情况。例如上面的user_name更新语句,给user_name添加普通索引:
-- 给user_name添加索引,缩小加锁范围 ALTER TABLE user_info ADD INDEX idx_user_name (user_name);
如果查询是范围查询,尽量让索引覆盖查询字段,减少回表操作,同时也能缩小锁的范围。
事务优化
尽量缩短事务的执行时间,减少锁的持有时间,避免在一个事务中执行多个无关的更新操作。同时尽量让不同事务的加锁顺序保持一致,避免死锁。例如两个事务都要更新用户A和用户B的余额,都按照用户ID从小到大的顺序加锁,就不会产生死锁。
隔离级别调整
如果业务允许,可以将事务隔离级别从可重复读调整为读已提交,这样可以减少间隙锁的使用,降低锁竞争的概率。调整语句如下:
-- 设置当前会话的隔离级别为读已提交 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
总结
mysql并发锁竞争导致的慢查询优化,核心是理清锁机制和索引的关联,通过合理的索引设计缩小加锁范围,再配合事务和隔离级别的优化,就能有效降低锁冲突的概率,提升数据库的并发性能。日常开发中也要注意规范SQL写法,避免无索引更新、大事务等问题,从源头减少锁竞争的产生。