mysql如何减少查询时的锁等待_mysql查询锁优化

2次阅读

select 会被锁住是因为在rr隔离级别下,带for update、lock in share mode或隐式当前读时会加行锁/间隙锁;若无索引则升级为临键锁。

mysql如何减少查询时的锁等待_mysql查询锁优化

为什么 SELECT 也会被锁住?

很多人以为只有 UPDATEdelete 才会加锁,其实 InnoDB 在可重复读(RR)隔离级别下,普通 SELECT 是快照读(不加锁),但一旦带上 FOR UPDATELOCK IN SHARE MODE,或者隐式升级为当前读(比如在唯一索引上做 UPDATE ... WHERE 前的条件检查),就会触发行锁甚至间隙锁。更隐蔽的是:如果查询走不到索引,可能退化为表级扫描,进而升级为临键锁(Next-Key Lock),锁住一大片范围。

如何快速定位正在阻塞你的查询?

别猜,直接查 information_schemaperformance_schema

SELECT r.trx_id waiting_trx_id,        r.trx_mysql_thread_id waiting_thread,        r.trx_query waiting_query,        b.trx_id blocking_trx_id,        b.trx_mysql_thread_id blocking_thread,        b.trx_query blocking_query FROM information_schema.INNODB_TRX b JOIN information_schema.INNODB_LOCK_WAITS w ON b.trx_id = w.blocking_trx_id JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;

配合下面这条看锁详情:

SELECT * FROM performance_schema.data_locks  WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';
  • 注意 LOCK_MODE 字段:是 RECORD(行锁)、GAP(间隙锁)还是 REC_NOT_GAP(仅记录锁)
  • LOCK_DATA 显示具体锁住的索引值,能帮你判断是不是因为没走索引导致锁范围过大

哪些写法会让 SELECT ... FOR UPDATE 锁得更久?

锁等待时间长,往往不是锁本身重,而是持有锁的事务拖得太久。以下写法极易踩坑:

  • 在事务里先执行 SELECT ... FOR UPDATE,然后做耗时操作(如 http 调用、文件读写、复杂计算),再才 UPDATE —— 锁从第一行 SELECT 就开始持有了
  • ORDER BY ... LIMIT 1 配合 FOR UPDATE,但 ORDER BY 字段没索引,导致全表扫描+全表加锁
  • WHERE 条件用了函数或类型隐式转换,例如 WHERE DATE(create_time) = '2024-01-01'WHERE user_id = '123'(user_id 是 int),导致索引失效,锁范围爆炸
  • 批量操作时用 IN 列表过长(比如几百个 ID),InnoDB 可能放弃使用索引而走全表扫描

真正有效的优化方向有哪些?

减少锁等待,核心是「缩小时长 + 缩小范围 + 避免冲突」:

  • SELECT ... FOR UPDATE 尽量靠近后续的 UPDATE,中间不要穿插业务逻辑;必要时拆成两个事务:先查,再根据结果决定是否开启新事务加锁更新
  • 确保所有 FOR UPDATE 查询都走索引——用 EXPLAINtype 是否为 ref/rangekey 是否命中预期索引
  • 并发扣减类场景(如库存),考虑用乐观锁替代:去掉 FOR UPDATE,改用 UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock >= 1,靠影响行数判断是否成功
  • 确认是否真需要 RR 隔离级别;若业务能接受读已提交(RC),则间隙锁禁用,大幅降低锁冲突概率(但需评估幻读风险)

最常被忽略的一点:锁等待超时默认是 50 秒(innodb_lock_wait_timeout),但很多应用层没设 query timeout,导致一个慢查询卡住整个线程池。务必在客户端连接配置里加上合理超时,而不是依赖 MySQL 默认值。

text=ZqhQzanResources