信息发布→ 登录 注册 退出

mysql并发下行锁和表锁如何选择_mysql锁粒度对比

发布时间:2025-12-30

点击量:
行锁会在索引失效时退化为表锁,如LIKE左模糊、隐式类型转换、函数操作字段等导致全表扫描;此时InnoDB加意向锁并实际锁全表。

行锁在什么情况下会退化成表锁

MySQL 的 InnoDB 默认用行锁,但不是所有带索引的 WHERE 条件都能命中行锁。一旦优化器判定无法使用索引、或用了不走索引的条件(比如 LIKE '%abc'、隐式类型转换、函数包裹字段),就会触发全表扫描,进而升级为表级意向锁 + 实际的表锁行为。

常见退化场景:

  • UPDATE user SET status=1 WHERE name LIKE '%admin%' —— name 有索引但 LIKE 左模糊导致索引失效
  • SELECT * FROM order WHERE user_id = '123',而 user_idINT 类型,传入字符串 '123' 触发隐式转换,索引失效
  • 事务中执行了未提交的 SELECT ... FOR UPDATE,但查询条件没走索引,InnoDB 会对所有聚集索引记录加锁,效果等同于锁表

INSERT … ON DUPLICATE KEY UPDATE 用的是行锁还是表锁

这个语句本质是「先尝试插入,冲突则更新」,InnoDB 对它做了专门优化:只对将要插入/更新的那几行加 next-key lock(间隙锁 + 行锁),不会锁整张表。

但要注意前提:

  • 必须有 PRIMARY KEYUNIQUE KEY,否则冲突判断无依据,语句会报错,更谈不上加什么锁
  • 冲突发生在唯一索引上时,InnoDB 会在该唯一索引对应记录上加 record lock;如果插入位置存在间隙,还会额外加 gap lock
  • 如果语句里 UPDATE 部分修改了唯一索引字段本身(比如 UPDATE id = id + 1),可能导致锁范围扩大,甚至触发死锁

如何用 SELECT … FOR UPDATE 精确控制行锁范围

SELECT ... FOR UPDATE 是否锁住多行、是否锁间隙,完全取决于你的 WHERE 条件是否能走索引,以及索引类型。

实操建议:

  • 只对主键或唯一索引字段做等值查询(如 WHERE id = 100),锁的是单条记录的 record lock
  • 对非唯一索引等值查询(如 WHERE status = 1),InnoDB 会加 next-key lock,即锁住匹配记录 + 该记录前的间隙,防止幻读
  • 范围查询(如 WHERE created_at > '2025-01-01')一定包含间隙锁,范围越大,锁越多,容易阻塞其他事务
  • 想避免间隙锁?可临时把事务隔离级别降为 READ COMMITTED(需确认业务能否接受不可重复读)

SHOW ENGINE INNODB STATUS 能看到哪些锁信息

这是诊断锁问题最直接的命令,输出里 TRANSACTIONS 部分会列出当前阻塞关系和持锁情况。重点关注:

  • Trx idTrx mysql thread id:定位具体哪个线程卡住了
  • Trx state: LOCK WAIT 表示正在等锁;Trx state: RUNNING 不代表没锁,只是当前没在等
  • lock_mode X locks rec but not gap:说明是纯行锁;lock_mode X locks gap before rec 是间隙锁;lock_mode X 单独出现大概率是表锁或意向锁
  • Record lock, heap no 123 中的 heap no 是聚簇索引堆记录号,结合 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX 可交叉验证
mysql> SHOW ENGINE INNODB STATUS\G
...
---TRANSACTION 42156789, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 123, OS thread handle 140234567890176, query id 456789 localhost root updating
UPDATE account SET balance = balance - 100 WHERE id = 5

上面这段输出里,“locked 1” 是指锁了一张表(逻辑),但实际是这张表里的某一行被 X 锁住了——粒度得看后面的 row lock(s) 数量和类型,不能只看 locked 1 就认为是表锁。

锁粒度不是写死的,它由查询路径、索引结构、隔离级别共同决定。很多人调优时只改 SQL 写法,却忘了检查执行计划是否真走了索引,或者忽略了 autocommit=0 下事务长期不提交导致锁一直挂着——这才是并发下锁问题最常被忽略的根因。

标签:# 类型转换  # 都能  # 走了  # 就会  # 这是  # 锁住  # 只对  # 隐式  # 死锁  # 会在  # 的是  # 并发  # mysql  # Thread  # 线程  #   # int  # 字符串  # select  # for  # sql  # 隐式转换  # 隐式类型转换  # ai  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!