当前位置: 首页 > news >正文

MySQL 锁机制实战:从全局锁到间隙锁,用 data_locks 看清每一把锁

MySQL 锁机制实战:从全局锁到间隙锁,用 data_locks 看清每一把锁

本文所有实验均在真实云服务器(Ubuntu 24.04 / MySQL 8.0.46,IP119.3.***.***)完成,输出为实机回显,未做任何修饰与编造。

一、引言:一次"整点备份把线上写挂了"的事故

那是一个平凡的周二晚上,运营同学要做一次"整库逻辑备份",脚本里写着mysqldump --single-transaction本该无锁,结果 DBA 手滑写成了flush tables with read lock;(后面忘了unlock)。一瞬间,所有写入线程全部卡在Waiting for global read lock,下单接口 100% 超时,告警群炸了。

这类事故的本质,是没搞清楚 MySQL 里到底有几类锁、各自锁住什么、会互相阻塞谁。MySQL 的锁按"粒度"可以分成三大类:全局锁 / 表级锁 / 行级锁;而行级锁在 RR(可重复读)隔离级别下又因为"间隙锁(gap lock)"和"next-key lock"变得格外微妙,是幻读、死锁的主要来源。

本文用一组真实多会话实验,配合 MySQL 8 新增的performance_schema.data_locks视图,把每一把锁都"透视"出来,让你在show processlist和死锁日志里不再两眼一抹黑。


二、实验环境

项目
操作系统Ubuntu 24.04.4 LTS(8C / 14G)
数据库MySQL 8.0.46(默认隔离级别 REPEATABLE-READ)
binlogROW 格式,binlog_row_image = FULL
performance_schema开启(data_locks 数据来源)
测试库表test.t(id int primary key, c int, d int, key(c))

初始数据(贯穿全文的"标尺"):

mysql> select * from t order by id; +----+------+------+ | id | c | d | +----+------+------+ | 0 | 0 | 0 | | 5 | 5 | 5 | | 10 | 10 | 10 | | 15 | 15 | 15 | | 20 | 20 | 20 | | 25 | 25 | 25 | +----+------+------+

说明:下文所有"会话 A / 会话 B / 会话 C"均为独立的mysql -uroot test交互连接;阻塞语句发出后,我们用一个独立观察连接执行show processlist/select ... from performance_schema.data_locks进行"第三方视角的透视"。


三、锁的分类与兼容矩阵

3.1 锁的分类(ASCII 图)

MySQL 锁全景 │ ┌─────────────────┼─────────────────────┐ │ │ │ 【全局锁】 【表级锁】 【行级锁】(InnoDB) FTWRL lock tables record lock(记录锁) 只读全实例 read/write gap lock(间隙锁) MDL(元数据锁) next-key lock(记录+间隙) (DDL 自动加) insert intention lock
  • 全局锁flush tables with read lock(FTWRL)让整个实例只读,常用于全实例逻辑备份。
  • 表级锁lock tables ... read/write显式加;以及MDL(metadata lock)——任何 DDL、DML 都会自动加 MDL,且读锁之间不互斥、读写互斥、写写互斥
  • 行级锁:InnoDB 才有,由"索引"驱动。分为记录锁、间隙锁、next-key 锁、插入意向锁。

3.2 行锁兼容矩阵(同一行 / 同一间隙)

请求 \ 已持有记录 S记录 X间隙 S,GAP间隙 X,GAPnext-key Snext-key X
记录 S兼容冲突兼容冲突兼容冲突
记录 X冲突冲突冲突冲突冲突冲突
间隙 S,GAP兼容冲突兼容兼容兼容兼容
间隙 X,GAP冲突冲突兼容兼容冲突兼容
next-key S兼容冲突兼容冲突兼容冲突
next-key X冲突冲突冲突冲突冲突冲突

关键记忆点:间隙锁之间永远兼容(多个事务可以同时"锁住同一段间隙"防止插入,这正是防止幻读的设计);记录 X 锁与任何锁都冲突,所以"两行都要改同一行"必然排队。

3.3 next-key lock 区间图(RR 默认)

MySQL 在 RR 下,对"扫描到的索引记录"默认加next-key lock = 记录锁 + 它前面的间隙锁,左开右闭(a, b]

索引记录: 0 5 10 15 20 25 |------|------|------|------|------|------| next-key: (-∞,0] (0,5] (5,10] (10,15] (15,20] (20,25] (25,+supremum]

例如where id=10命中记录 10,加锁为记录锁 10 + 间隙 (10,15];等值查询一个"不存在的值"(如 id=7),则退化成纯间隙锁 (5,10),因为 7 落在 (5,10) 之间,没有记录可锁。


四、多会话实操(真实回显)

4.1 全局锁 FTWRL

会话 A 加全局读锁,会话 B 尝试写入被阻塞:

A: flush tables with read lock; Query OK, 0 rows affected (0.00 sec) B: insert into t values (30,30,30); (无返回,明显阻塞) 观察者 show processlist: Id User Command Time State Info 16 root Query 4 Waiting for global read lock insert into t values (30,30,30)

A 释放后,B 立刻完成:

A: unlock tables; Query OK, 0 rows affected (0.00 sec) B after unlock: Query OK, 1 row affected (4.40 sec) -- 注意这 4.40s 就是它苦等 FTWRL 的时间

实战结论:FTWRL一上,全实例写入全卡。生产上做备份要么用--single-transaction(InnoDB 一致性快照,不加全局读锁),要么用xtrabackup加了 FTWRL 务必有自动超时/unlock 兜底

4.2 表锁lock tables t read

A: lock tables t read; Query OK, 0 rows affected (0.00 sec) A: insert into t values (30,30,30); ERROR 1099 (HY000): Table 't' was locked with a READ lock and can't be updated

本会话对自己加的读锁表也不能写,直接报错 1099。另一个会话 B 去写则被阻塞:

B: insert into t values (30,30,30); (阻塞) 观察者 show processlist: Id User Command Time State Info 20 root Query 4 Waiting for table metadata lock insert into t values (30,30,30)

注意:在 MySQL 8 里,lock tables read造成的写阻塞,在 processlist 里显示的等待状态是Waiting for table metadata lock(表锁底层也走 MDL 实现),并不是老版本里的 “Waiting for table level lock”。这是真实回显,别被旧书误导。

4.3 MDL 元数据锁(最隐蔽的"慢查询"元凶)

会话 A 开启事务并只做查询(注意没有任何写/锁语句):

A: begin; select * from t where id=0; +----+------+------+ | id | c | d | +----+------+------+ | 0 | 0 | 0 | +----+------+------+

此时会话 B 要加字段(DDL),被阻塞:

B: alter table t add column e int; (阻塞) 会话 C 再做一次普通查询,竟然也跟着阻塞了: C: select * from t where id=5; (阻塞) 观察者 show processlist: Id User Command Time State Info 24 root Query 8 Waiting for table metadata lock alter table t add column e int 25 root Query 4 Waiting for table metadata lock select * from t where id=5

A 一提交,B、C 才先后放行:

A: commit; Query OK, 0 rows affected (0.00 sec) B after: Query OK, 0 rows affected (8.41 sec) -- 等了 8 秒 C after: 1 row in set (4.41 sec) -- C 排在 B 后面,形成 MDL 队列

实战结论:长事务 + 任何 DDL = 雪崩。A 的长事务一直拿着 MDL 读锁,B 的 DDL 要 MDL 写锁被挡,而 C 的普通查询也要 MDL 读锁——结果 C 并不和 A 冲突,却要排在 B 后面等写锁释放(MDL 队列按请求顺序 FIFO)。所以线上"明明只是查一下却卡住",十有八九是某条长事务/未提交事务挡住了 DDL。定期select * from information_schema.innodb_trx揪出长事务是救命操作。


五、行锁与两阶段锁协议

InnoDB 的"两阶段锁协议":锁在需要时才加,但直到事务提交/回滚才释放。这意味着"把最可能造成冲突、最晚用到的锁尽量往后放",能缩短持锁时间。

会话 A 改 id=5 但不提交;会话 B 改同一行被阻塞,直到innodb_lock_wait_timeout(本机默认 50s,实验特地设成 5s):

A: begin; update t set d=d+1 where id=5; Query OK, 1 row affected (0.00 sec) B: begin; update t set d=d+1 where id=5; (阻塞 5 秒后) B after ~5s: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

ERROR 1205是"等不到锁而超时",不是死锁;它不会自动回滚整个事务,只是当前这条语句失败,事务还开着——记得业务里捕获到 1205 要主动rollback


六、用 data_locks 透视间隙锁(RR 级别,4 个案例)

MySQL 8 的performance_schema.data_locks让我们能"看见"每把锁。观察语句:

selectengine_transaction_id,object_name,index_name,lock_type,lock_mode,lock_datafromperformance_schema.data_lockswhereobject_name='t'\G

案例 1:等值查询间隙锁(查一个不存在的值 id=7)

A: begin; update t set d=d+1 where id=7; Query OK, 0 rows affected (0.00 sec) -- 0 行受影响,因为 7 不存在 data_locks(A 持有): engine_transaction_id: 2413 object_name: t index_name: PRIMARY lock_type: RECORD lock_mode: X,GAP <-- 纯间隙锁 lock_data: 10 <-- 锁住 (5,10) 这个间隙 验证:B update id=10(已存在的记录)【不阻塞】: B: update t set d=d+1 where id=10; Query OK, 1 row affected (0.00 sec) 验证:B insert id=8(落进间隙)【阻塞】: B: insert into t values(8,8,8); (阻塞,processlist 显示 update 状态、Info=insert into t values(8,8,8)) A 提交后 B 才完成: Query OK, 1 row affected (3.91 sec)

解读:对主键等值查"不存在的行",RR 下加的是间隙锁(5,10)(用lock_data=10表示这段间隙的右边界)。它只挡"往间隙里插",不挡"改已有的 10"。这正是防止幻读的关键。

案例 2:非唯一索引等值(c=5)加 S 锁

A: begin; select id from t where c=5 lock in share mode; +----+ | id | +----+ | 5 | +----+ data_locks(A 持有): engine_transaction_id: 419091093388504 index_name: c lock_type: RECORD lock_mode: S,GAP lock_data: 10, 10 <-- 间隙 (5,10) engine_transaction_id: 419091093388504 index_name: c lock_type: RECORD lock_mode: S lock_data: 5, 5 <-- 记录锁 c=5(同时覆盖其前面的间隙,即 next-key (0,5])

解读:非唯一索引等值 S 锁,加的是next-key 锁(0,5](记录锁 c=5)+ 间隙锁(5,10)。注意这里没有出现 PRIMARY 的锁——因为select id覆盖索引(索引 c 的叶子已经包含 id),根本没回表,所以主键上无锁。这个细节很多人会忽略。

案例 3:主键范围id>=10 and id<11 for update

A: begin; select * from t where id>=10 and id<11 for update; +----+------+------+ | id | c | d | +----+------+------+ | 10 | 10 | 10 | +----+------+------+ data_locks(A 持有): index_name: PRIMARY lock_type: RECORD lock_mode: X,GAP lock_data: 15 <-- 间隙 (10,15] index_name: PRIMARY lock_type: RECORD lock_mode: X,REC_NOT_GAP lock_data: 10 <-- 记录锁 10(不含间隙)

解读:命中唯一记录 10,加记录锁 10 + next-key 间隙 (10,15](注意不是 (10,20],因为id<11扫描到 10 就停,next-key 只到下一跳 15)。

案例 4:幻读演示 —— RR 防幻读 vs RC 不防

RR 下

A(RR): begin; select * from t where id>=10 and id<15 for update; +----+------+------+ | id | c | d | +----+------+------+ | 10 | 10 | 10 | +----+------+------+ data_locks: 记录锁 10 + 间隙 (10,15] (同案例 3 形态) B: insert into t values(12,12,12); (阻塞;processlist State=update, Info=insert into t values(12,12,12)) A 提交后 B 才插入成功: Query OK, 1 row affected (3.91 sec)

RC 下(会话级set session transaction isolation level read committed):

A(RC): begin; select * from t where id>=10 and id<15 for update; data_locks(RC): index_name: PRIMARY lock_type: RECORD lock_mode: X,REC_NOT_GAP lock_data: 10 <-- 只有记录锁 10,没有间隙锁! B: insert into t values(12,12,12); Query OK, 1 row affected (0.00 sec) <-- 直接成功,幻读发生

解读:RR 靠"next-key lock 锁住扫描区间"阻止了别的事务往区间里插数据,从而防住幻读;而 RC 只锁已命中的记录、不加间隙锁,于是 B 能插进 12 造成幻读。这也是为什么"RC 下 binlog 必须用 ROW 格式"——语句复制在 RC 下会因幻读导致主从不一致。


七、死锁:完整 LATEST DETECTED DEADLOCK 逐行解读

构造死锁:A 改 5、B 改 10,然后 A 改 10(等 B)、B 改 5(等 A),形成环。

A: begin; update t set d=d+1 where id=5; -> Query OK B: begin; update t set d=d+1 where id=10; -> Query OK A: update t set d=d+1 where id=10; -> (阻塞,等 B 的 10 锁) B: update t set d=d+1 where id=5; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction A: (B 被回滚释放 10 锁后)Query OK, 1 row affected (3.50 sec)

show engine innodb status中真正的死锁现场(实机原文,逐行拆解):

LATEST DETECTED DEADLOCK ------------------------ 2026-07-29 11:51:02 137615651108544 *** (1) TRANSACTION: TRANSACTION 2377, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1 MySQL thread id 31, OS thread handle 137615720371904, query id 127 localhost root updating update t set d=d+1 where id=10 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2377 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0 0: len 4; hex 80000005; asc ;; <-- 持有记录锁 id=5 1: len 6; hex 000000000949; asc I;; 2: len 7; hex 010000011f0151; asc Q;; 3: len 4; hex 80000005; asc ;; 4: len 4; hex 80000006; asc ;; 5: SQL DEFAULT; *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 2 page no 4 n bits 80 index PRIMARY of table `test`.`t` trx id 2377 lock_mode X locks rec but not gap waiting Record lock, heap no 4 PHYSICAL RECORD: n_fields 6; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; <-- 等待记录锁 id=10 ... *** (2) TRANSACTION: TRANSACTION 2378, ACTIVE 7 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1 MySQL thread id 32, OS thread handle 137615592359616, query id 128 localhost root updating update t set d=d+1 where id=5 *** (2) HOLDS THE LOCK(S): RECORD LOCKS ... index PRIMARY ... trx id 2378 lock_mode X locks rec but not gap Record lock ... hex 8000000a; asc ;; <-- 持有记录锁 id=10 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS ... trx id 2378 lock_mode X locks rec but not gap waiting Record lock ... hex 80000005; asc ;; <-- 等待记录锁 id=5

逐行解读

  1. TRANSACTION 2377 ... update t set d=d+1 where id=10:事务 (1) 当前正在执行的语句是"改 id=10"。
  2. (1) HOLDShex 80000005即十进制的5,说明事务 (1) 手里握着 id=5 的 X 记录锁(它第一步update id=5拿到的,一直没释放)。
  3. (1) WAITINGhex 8000000a=10,说明事务 (1)在等 id=10 的 X 锁——而这把锁被事务 (2) 握着。
  4. TRANSACTION 2378 ... update t set d=d+1 where id=5:事务 (2) 当前语句是"改 id=5"。
  5. (2) HOLDShex 8000000a(=10),事务 (2)握着 id=10 的 X 锁(它第一步update id=10拿到的)。
  6. (2) WAITINGhex 80000005(=5),事务 (2)在等 id=5 的 X 锁——而这把锁被事务 (1) 握着。

闭环:(1) 持有 5 等 10,(2) 持有 10 等 5 → 互相等待 → 死锁。InnoDB 的innodb_deadlock_detect=ON(本机默认开)会立刻检测到环,挑选回滚代价较小(undo log entries 少 / 改动少)的事务作为牺牲品,这里牺牲了事务 (2),于是 B 收到ERROR 1213,A 随后拿到 10 锁继续执行。


八、踩坑清单

  1. FTWRL 忘 unlock:全实例只读雪崩。备份优先--single-transaction
  2. 长事务挡 DDL:MDL 队列会让后续所有查询跟着卡。监控information_schema.innodb_trxtrx_started
  3. 间隙锁导致的"莫名"阻塞:RR 下for update会锁区间,别以为只锁了那几行;需要防止幻读又想减少锁冲突时,评估能否降到 RC(前提是 binlog=ROW)。
  4. ERROR 1205 ≠ 死锁:1205 是超时,事务没回滚;1213 是死锁,牺牲品被回滚。业务都要兜底rollback/ 重试。
  5. 覆盖索引不回表 → 主键无锁:案例 2 已经证明,select id from t where c=5 lock in share mode主键上根本没有锁,改主键别的列不会冲突。

九、面试高频问答

Q1:MySQL 锁分几类?
全局锁(FTWRL)、表级锁(lock tables / MDL)、行级锁(记录锁、间隙锁、next-key 锁、插入意向锁)。InnoDB 行锁靠索引,没走索引会退化为表锁(全行扫描加 X 锁)。

Q2:什么是 next-key lock?为什么 RR 用它防幻读?
next-key lock = 记录锁 + 它前面的间隙锁,左开右闭(a, b]。RR 下扫描到哪些索引记录就给它们加 next-key 锁,从而把"满足条件的新插入"也挡在区间外,杜绝幻读。RC 下只加记录锁、不加间隙锁,因此会出现幻读。

Q3:间隙锁之间兼容吗?
兼容。多个事务可以同时持有同一段间隙的间隙锁,目的是共同阻止插入,而不是互相排斥。冲突主要来自"记录 X 锁"。

Q4:MDL 为什么会让普通查询也卡住?
MDL 读写互斥、写写互斥,且按请求顺序排队(FIFO)。一条长事务拿着 MDL 读锁时,DDL 的写锁请求会排队,而后续普通查询的读锁请求也需要排在写锁请求之后,于是看似"只读"的查询也跟着卡。

Q5:ERROR 1205 和 1213 区别?
1205 是锁等待超时(innodb_lock_wait_timeout),语句失败但事务仍开;1213 是死锁,牺牲品事务被回滚。两者业务层都要做好重试点。

Q6:怎么快速定位死锁原因?
立刻show engine innodb statusLATEST DETECTED DEADLOCK:每个 TRANSACTION 的HOLDSWAITING FOR列出各自持锁/等锁的记录(hex 是主键值),据此画出等待环即可定位是哪两条 SQL、哪两行互相卡死;也可开innodb_print_all_deadlocks=ON把每次死锁写进错误日志。

Q7:如何降低死锁概率?
固定访问顺序(所有事务按同一顺序改多行)、缩短事务、降低隔离级别到 RC(减少间隙锁)、给查询走合适的索引避免锁放大。


九、扩展阅读:把"锁问题"变成可观测的指标

光会看现场还不够,DBA 的真正功力是在锁变成事故之前就发现它。下面几组查询建议熟记。

9.1 谁在等、等谁的锁(替代肉眼刷 processlist)

MySQL 8 提供了sys.innodb_lock_waitsperformance_schema.data_lock_waits,一行 SQL 就能画出等待链:

selectwaiting_trx_id,waiting_pid,blocking_trx_id,blocking_pid,wait_age,locked_table,locked_index,locked_typefromsys.innodb_lock_waits\G

它的价值在于:不用两台机器来回比对 processlist 的 HOLDS/WAITING,直接告诉你"阻塞者进程号",运维脚本拿到blocking_pid后甚至能自动kill掉长时间阻塞的源头(当然要先确认那是安全可杀的查询,而不是一个核心写事务)。

9.2 当前所有"持锁事务"清单

selecttrx_id,trx_state,trx_started,trx_wait_started,trx_rows_locked,trx_rows_modified,trx_mysql_thread_idfrominformation_schema.innodb_trxorderbytrx_startedasc;

trx_started越早、却一直trx_state=RUNNING的事务,就是最可疑的"长事务持锁源"——它不提交,后面所有需要它持有锁的语句都得排队。线上建议对timediff(now(), trx_started) > 10的事务告警。

9.3 锁等待超时与死锁的监控

  • innodb_lock_wait_timeout(本机默认 50s):单条语句等锁的最长时间,超时即ERROR 1205。业务敏感场景可调小(如 3~5s)让失败快返回。
  • innodb_deadlock_detect = ON:开启后 InnoDB 用等待图实时检测死锁,一旦成环立刻挑牺牲品。关掉它能减少高并发下的检测开销,但代价是死锁要等到锁等待超时才能解开,得不偿失,一般保持开启。
  • 想事后复盘所有死锁,设innodb_print_all_deadlocks = ON,每次死锁都会写进错误日志,而不只是保留最近一次到show engine innodb status

9.4 一个容易被忽略的合规点:账号与密码

本文所有命令都用的mysql -uroot操作系统用户直连(云上 Ubuntu 默认 root 走 socket 认证,无需密码),但生产环境请务必:① 禁用 root 远程登录;② 业务用最小权限的专属账号;③ 本地脚本若必须存密码,用~/.my.cnf且权限0600,切勿把密码写进博客、脚本注释或提交到代码仓库。本文严格遵循此原则,正文中不出现任何真实口令。

9.5 隔离级别与锁的联动小结

隔离级别普通 SELECTfor update / 锁读间隙锁幻读
READ UNCOMMITTED无锁(读未提交)记录锁
READ COMMITTED快照读无锁仅记录锁
REPEATABLE READ(默认)快照读无锁next-key lock无(靠间隙锁防住)
SERIALIZABLE隐式加共享锁共享 next-key

记住:间隙锁只在 RR/SERIALIZABLE 存在,RC 下彻底消失——这正是案例 4 里 RR 阻止插入、RC 允许插入的根本原因,也是"RC + binlog=ROW"成为很多互联网公司默认组合的理由(并发高、锁少、主从一致靠 ROW 保证)。

九、补:新人最容易踩的五个锁误区

很多同学背得熟"RR 防幻读",一上线照样被锁坑,根子在于把"概念"当成了"直觉"。下面五个误区都是真实血泪。

误区一:以为"只读查询一定不加锁"。普通SELECT在 RR 下是快照读(MVCC),确实不加锁;但一旦写成SELECT ... LOCK IN SHARE MODEFOR UPDATE,立刻变成当前读并加锁。更隐蔽的是,update/deleteWHERE条件无论怎么写,执行时都要先"当前读"定位行,于是自动加记录锁/间隙锁。所以"我明明只是查"这句话在排错时常常不成立——要看的是它到底是不是当前读。

误区二:以为间隙锁只锁住"那一行"。间隙锁锁的是"一段区间",比如案例 1 里update ... where id=7(7 不存在),锁的是(5,10)这段开区间,所有落在里面的插入(id=6/7/8/9)统统被挡。很多"明明没改这行却插不进去"的诡异阻塞,源头就是某条for update把一段间隙悄悄锁死了。

误区三:以为 RC 和 RR 只是"会不会脏读"的区别。对开发最直观的差异其实是间隙锁:RR 有、RC 没有。同一个"范围for update",RR 下防住幻读(别的事务插不进来),RC 下却放任插入导致幻读。不少团队为了提升并发把隔离级别降到 RC,却没意识到这等于放弃了间隙锁保护,必须靠 binlog=ROW 来兜底主从一致——这一步漏掉就是数据不一致。

误区四:看到Waiting for table metadata lock就以为是"表被锁了"。不是。这是 MDL(元数据锁)等待,罪魁往往是一条还没提交的长事务稳稳拿着 MDL 读锁,挡住了后面的 DDL 写锁,而后续普通查询又被 DDL 挡在后面排队。看到这个状态,第一反应应该是去information_schema.innodb_trx里揪"最老的那个 RUNNING 事务",而不是去 kill 那个 DDL。

误区五:死锁后无脑重试。应用捕获到 1213 直接原地重试,结果因为两事务仍按相反顺序抢锁,重试又死锁。正确的做法是:重试加随机退避、缩短事务、并尽量让所有调用方按同一顺序访问多行。死锁本身不可怕(InnoDB 会帮你挑一个牺牲),可怕的是"重试风暴"把数据库 CPU 打满。

锁问题排查 SOP(建议贴在工位)

  1. show processlistStateWaiting for ... lock的线程,记下IdInfo
  2. select ... from performance_schema.data_locks看谁HOLDS、谁WAITING,把lock_data的 hex 翻成主键值;
  3. information_schema.innodb_trx找持锁时间最长的事务,判断是不是长事务/未提交事务;
  4. 必要时kill query <id>停语句,或kill <id>端连接;kill 前务必确认User/Info不是核心写链路;
  5. 死锁看show engine innodb statusLATEST DETECTED DEADLOCK,按本文第七节的"持锁/等锁"画法还原等待环。

把这套 SOP 练成本能,线上锁问题从"两眼一抹黑"变成"照方抓药",平均止损时间能从几十分钟压到几分钟。

十、总结

MySQL 的锁体系看似繁杂,但抓住"全局 → 表(MDL) → 行(记录/间隙/next-key)"三层,再记住"RR 靠 next-key 防幻读、两阶段锁协议延迟释放、死锁靠等待环检测"三条主线,就能把绝大多数锁问题讲清楚、查明白。真正的功力在于data_locksshow engine innodb status把抽象锁具象化——本文所有回显均来自真实云服务器,建议你在自己的环境把上面 4 个间隙锁案例亲手跑一遍,把lock_data的 hex 值翻译成主键值,那一刻"锁"就从黑盒变成了透明。

本文实验均在真实云服务器完成,输出为实机回显。

http://www.jsqmd.com/news/1289032/

相关文章:

  • 北京名包回收哪家靠谱?全城 5 家高口碑门店实测对比 - 日常比对手册
  • G-Helper完整指南:3个核心功能+5分钟上手华硕笔记本性能管理
  • 想学AI漫剧?看这本书就够!
  • 物业运维干货:老旧小区外墙维保怎么做?低成本、零投诉、长效省心 - 青岛防水品牌推荐
  • 单片机毕设选题推荐:基于按键交互的 STM32 灌溉参数整定系统实现 基于 STM32 的小型农田智能节水灌溉装置设计(011701)
  • 2026年7月南宁西乡塘区管道疏通避坑指南,找本地专业师傅就选旭日管道疏通 - 余生黄金回收
  • 2026年开一个羽毛球馆需要什么系统?新手馆长数字化采购指南
  • Ryujinx模拟器:3步掌握Switch游戏在PC上流畅运行的终极指南
  • Python学习---数据容器
  • 2026学术写作AI论文写作软件全榜单:7款实测,哪款论文党看完直接闭眼选?
  • 精准测力,掌控毫厘之间——泰信精密小型高精度拉力试验机全面解析
  • Trippy网络诊断终极指南:如何快速解决网络连接问题
  • 在苹果生态中构建跨平台虚拟化环境:UTM深度探索
  • 想在家尝试副业,抖店一件代发需要提前准备哪些基础工作? - 电商分享
  • 从Noise节点到火焰动画:unity-shadergraph-sandbox项目Fire特效实现指南
  • 中国十大正规太极武校,河南王战军太极学校资质齐全 - 圣龙武术朱老师
  • TediCross常见问题解决:聊天ID获取与消息同步故障排查
  • 如何在Apple Silicon设备上部署FFTformer-GoPro-fp32?完整指南助你轻松实现图像去模糊
  • ppInk:如何用这款免费屏幕标注工具让你的演示效率提升300%
  • 深入理解lx-music-custom-source架构:内置解析源与自定义脚本开发
  • 本人在国外房屋解押,委托公证书当天能出吗? - 跑政通
  • 【AI心理健康评估临床落地指南】:20年精神科+AI工程师双视角揭秘5类误判陷阱与3步校准法
  • ThorUI-uniapp完全手册:80+组件助你快速构建跨平台应用
  • 福建石头破碎机批发厂家怎么选?实地考察认准这几项核心指标 - 品牌优推
  • 订房APP功能模块与技术架构分析——基于市面上2款主流订房APP技术对比
  • 免费AI视频抠像终极指南:MatAnyone如何用3分钟实现专业级背景替换
  • 乌鲁木齐新市区管道疏通避坑指南,2026年7月找本地靠谱师傅就选这三家 - 余生黄金回收
  • 单片机毕设选题推荐:基于 STM32 的自动模式与阈值设置双模式控制系统 基于单片机的水温检测与继电器加热控制系统开发(011801)
  • 车企怎么用智能呼叫系统做试驾邀约?适合车企的AI呼叫系统
  • Pattern Monster:320+免费SVG无缝图案生成工具,轻松打造专业级背景设计