【计算基础|MySQL14】MySQL 锁(上):种类与加锁规则

【计算基础|MySQL14】MySQL 锁(上):种类与加锁规则

前两篇用实验把事务隔离和锁现象跑通了。这篇回到原理,回答一个面试必问、生产必踩的问题:MySQL 到底有哪些锁?一条 SQL 发出去,InnoDB 凭什么决定锁哪里、锁多大范围?

很多人学锁是乱的:一会儿行锁一会儿表锁,一会儿间隙锁一会儿意向锁,名词堆了一堆却串不起来。这篇先用一张分类树把所有锁摆上桌,再按"全局锁→表级锁→行级锁"三层往下拆,最后给 7 条加锁规则。看完你应该能看着一条 UPDATE SQL,在脑子里说出它会加哪些锁、锁哪些记录。

环境:本文原理基于 MySQL 8.0 InnoDB,RR 隔离级别(默认)。涉及 data_locks 的结论来自上一篇实战复现。


一、先把锁的分类树画出来

MySQL 的锁不是一个平面概念,而是从多个维度切出来的。一张图看全:
#mermaid-svg-T7OSadYoMhUBOnhH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-T7OSadYoMhUBOnhH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-T7OSadYoMhUBOnhH .error-icon{fill:#552222;}#mermaid-svg-T7OSadYoMhUBOnhH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-T7OSadYoMhUBOnhH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-T7OSadYoMhUBOnhH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-T7OSadYoMhUBOnhH .marker.cross{stroke:#333333;}#mermaid-svg-T7OSadYoMhUBOnhH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-T7OSadYoMhUBOnhH p{margin:0;}#mermaid-svg-T7OSadYoMhUBOnhH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-T7OSadYoMhUBOnhH .cluster-label text{fill:#333;}#mermaid-svg-T7OSadYoMhUBOnhH .cluster-label span{color:#333;}#mermaid-svg-T7OSadYoMhUBOnhH .cluster-label span p{background-color:transparent;}#mermaid-svg-T7OSadYoMhUBOnhH .label text,#mermaid-svg-T7OSadYoMhUBOnhH span{fill:#333;color:#333;}#mermaid-svg-T7OSadYoMhUBOnhH .node rect,#mermaid-svg-T7OSadYoMhUBOnhH .node circle,#mermaid-svg-T7OSadYoMhUBOnhH .node ellipse,#mermaid-svg-T7OSadYoMhUBOnhH .node polygon,#mermaid-svg-T7OSadYoMhUBOnhH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-T7OSadYoMhUBOnhH .rough-node .label text,#mermaid-svg-T7OSadYoMhUBOnhH .node .label text,#mermaid-svg-T7OSadYoMhUBOnhH .image-shape .label,#mermaid-svg-T7OSadYoMhUBOnhH .icon-shape .label{text-anchor:middle;}#mermaid-svg-T7OSadYoMhUBOnhH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-T7OSadYoMhUBOnhH .rough-node .label,#mermaid-svg-T7OSadYoMhUBOnhH .node .label,#mermaid-svg-T7OSadYoMhUBOnhH .image-shape .label,#mermaid-svg-T7OSadYoMhUBOnhH .icon-shape .label{text-align:center;}#mermaid-svg-T7OSadYoMhUBOnhH .node.clickable{cursor:pointer;}#mermaid-svg-T7OSadYoMhUBOnhH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-T7OSadYoMhUBOnhH .arrowheadPath{fill:#333333;}#mermaid-svg-T7OSadYoMhUBOnhH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-T7OSadYoMhUBOnhH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-T7OSadYoMhUBOnhH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-T7OSadYoMhUBOnhH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-T7OSadYoMhUBOnhH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-T7OSadYoMhUBOnhH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-T7OSadYoMhUBOnhH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-T7OSadYoMhUBOnhH .cluster text{fill:#333;}#mermaid-svg-T7OSadYoMhUBOnhH .cluster span{color:#333;}#mermaid-svg-T7OSadYoMhUBOnhH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-T7OSadYoMhUBOnhH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-T7OSadYoMhUBOnhH rect.text{fill:none;stroke-width:0;}#mermaid-svg-T7OSadYoMhUBOnhH .icon-shape,#mermaid-svg-T7OSadYoMhUBOnhH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-T7OSadYoMhUBOnhH .icon-shape p,#mermaid-svg-T7OSadYoMhUBOnhH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-T7OSadYoMhUBOnhH .icon-shape .label rect,#mermaid-svg-T7OSadYoMhUBOnhH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-T7OSadYoMhUBOnhH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-T7OSadYoMhUBOnhH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-T7OSadYoMhUBOnhH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MySQL 锁
按粒度
按模式
按态度
按 InnoDB 实现
全局锁 FTWRL
表级锁
行级锁
共享锁 S / 读锁
排他锁 X / 写锁
悲观锁: 先加锁再操作
乐观锁: 版本号/CAS
LOCK TABLES 显式表锁
MDL 元数据锁
意向锁 IS / IX
AUTO-INC 自增锁
记录锁 Record Lock
间隙锁 Gap Lock
Next-Key Lock
插入意向锁

🔴 这张图是本篇的骨架。下面按粒度从粗到细讲。


二、全局锁:FLUSH TABLES WITH READ LOCK

结论 :全局锁就是把整个数据库实例设成只读,几乎只用于全库逻辑备份。

sql 复制代码
FLUSH TABLES WITH READ LOCK;   -- 加全局读锁
-- 此时所有库的所有写操作(INSERT/UPDATE/DDL)全部阻塞
UNLOCK TABLES;                  -- 释放
项 说明
作用范围 整个实例,所有库所有表
谁阻塞谁 加锁后,所有会话的写操作、DDL 全部阻塞,只能读
典型用途 全库逻辑备份(MyISAM 不支持事务,只能靠它保证备份一致性)

⚠️ 避坑 :现在用 InnoDB,备份不需要全局锁。mysqldump --single-transaction 靠事务一致性快照(RR 下的 ReadView)就能拿到一致性备份,全程不加全局锁、不阻塞写 。只有 MyISAM 这种不支持事务的引擎才不得不 FTWRL。线上千万别手贱敲 FTWRL,否则业务全挂。


三、表级锁

3.1 显式表锁:LOCK TABLES

sql 复制代码
LOCK TABLES t READ;    -- 加表读锁,其他会话能读不能写
LOCK TABLES t WRITE;   -- 加表写锁,其他会话读写都阻塞
UNLOCK TABLES;

🔴 InnoDB 一般不用这个。它和 InnoDB 的行锁模型冲突(InnoDB 自己管行锁,你再手动锁表等于把并发废掉)。知道有这东西就行,生产基本见不到。

3.2 MDL 元数据锁(重点坑)

结论 :访问一张表时 InnoDB/Server 层自动加 MDL,读操作加 MDL 读锁(S),DDL 加 MDL 写锁(X);MDL 锁要到事务提交才释放。这是线上"一个 ALTER 把库打满"的元凶。

机制:
会话C(ALTER) 会话B(新查询) MySQL 会话A(长查询) 会话C(ALTER) 会话B(新查询) MySQL 会话A(长查询) #mermaid-svg-6nsLjdvhAFi327if{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6nsLjdvhAFi327if .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6nsLjdvhAFi327if .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6nsLjdvhAFi327if .error-icon{fill:#552222;}#mermaid-svg-6nsLjdvhAFi327if .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6nsLjdvhAFi327if .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6nsLjdvhAFi327if .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6nsLjdvhAFi327if .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6nsLjdvhAFi327if .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6nsLjdvhAFi327if .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6nsLjdvhAFi327if .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6nsLjdvhAFi327if .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6nsLjdvhAFi327if .marker.cross{stroke:#333333;}#mermaid-svg-6nsLjdvhAFi327if svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6nsLjdvhAFi327if p{margin:0;}#mermaid-svg-6nsLjdvhAFi327if .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6nsLjdvhAFi327if text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6nsLjdvhAFi327if .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6nsLjdvhAFi327if .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6nsLjdvhAFi327if .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6nsLjdvhAFi327if .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6nsLjdvhAFi327if #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6nsLjdvhAFi327if .sequenceNumber{fill:white;}#mermaid-svg-6nsLjdvhAFi327if #sequencenumber{fill:#333;}#mermaid-svg-6nsLjdvhAFi327if #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6nsLjdvhAFi327if .messageText{fill:#333;stroke:none;}#mermaid-svg-6nsLjdvhAFi327if .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6nsLjdvhAFi327if .labelText,#mermaid-svg-6nsLjdvhAFi327if .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6nsLjdvhAFi327if .loopText,#mermaid-svg-6nsLjdvhAFi327if .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6nsLjdvhAFi327if .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6nsLjdvhAFi327if .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6nsLjdvhAFi327if .noteText,#mermaid-svg-6nsLjdvhAFi327if .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6nsLjdvhAFi327if .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6nsLjdvhAFi327if .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6nsLjdvhAFi327if .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6nsLjdvhAFi327if .actorPopupMenu{position:absolute;}#mermaid-svg-6nsLjdvhAFi327if .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6nsLjdvhAFi327if .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6nsLjdvhAFi327if .actor-man circle,#mermaid-svg-6nsLjdvhAFi327if line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6nsLjdvhAFi327if :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} A 事务一直没提交,MDL S 不释放 X 与 S 互斥,C 排队等 A 新 S 和排队的 X 也互斥!B 被堵住 SELECT ... FROM t (拿 MDL 读锁S)ALTER TABLE t (要 MDL 写锁X)SELECT ... FROM t (也要 MDL 读锁S)后面所有新查询全部堵死,连接迅速打满

🔴 致命点:MDL 读锁之间是兼容的,但一旦中间来了一个要 MDL 写锁的 ALTER,后面所有新来的 MDL 读锁都得排队等那个写锁------因为写锁优先级高,不能让新读锁插队。于是"一个慢查询 + 一个 ALTER" = 整个表的业务瘫痪。

怎么解决:

  1. ALTER 之前先查有没有长事务:SELECT * FROM information_schema.innodb_trx;
  2. 大表 DDL 别用原生 ALTER,用 pt-online-schema-change 或 gh-ost,它们通过影子表+行级增量同步,不长期持有 MDL 写锁。
  3. MySQL 8.0 支持 LOCK WAIT N / NOWAIT:ALTER TABLE t NOWAIT ADD COLUMN ...,拿不到 MDL 写锁就立刻报错,不排队堵业务。

3.3 意向锁:IS / IX

结论 :意向锁是表级锁,声明"我待会要在这张表的某些行上加 S/X 锁"。它的唯一作用是让表锁能快速判断要不要和行锁冲突,不用逐行检查。
#mermaid-svg-ZsVGxeMSkdAb0RBp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZsVGxeMSkdAb0RBp .error-icon{fill:#552222;}#mermaid-svg-ZsVGxeMSkdAb0RBp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZsVGxeMSkdAb0RBp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .marker.cross{stroke:#333333;}#mermaid-svg-ZsVGxeMSkdAb0RBp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZsVGxeMSkdAb0RBp p{margin:0;}#mermaid-svg-ZsVGxeMSkdAb0RBp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster-label text{fill:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster-label span{color:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster-label span p{background-color:transparent;}#mermaid-svg-ZsVGxeMSkdAb0RBp .label text,#mermaid-svg-ZsVGxeMSkdAb0RBp span{fill:#333;color:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .node rect,#mermaid-svg-ZsVGxeMSkdAb0RBp .node circle,#mermaid-svg-ZsVGxeMSkdAb0RBp .node ellipse,#mermaid-svg-ZsVGxeMSkdAb0RBp .node polygon,#mermaid-svg-ZsVGxeMSkdAb0RBp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .rough-node .label text,#mermaid-svg-ZsVGxeMSkdAb0RBp .node .label text,#mermaid-svg-ZsVGxeMSkdAb0RBp .image-shape .label,#mermaid-svg-ZsVGxeMSkdAb0RBp .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZsVGxeMSkdAb0RBp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .rough-node .label,#mermaid-svg-ZsVGxeMSkdAb0RBp .node .label,#mermaid-svg-ZsVGxeMSkdAb0RBp .image-shape .label,#mermaid-svg-ZsVGxeMSkdAb0RBp .icon-shape .label{text-align:center;}#mermaid-svg-ZsVGxeMSkdAb0RBp .node.clickable{cursor:pointer;}#mermaid-svg-ZsVGxeMSkdAb0RBp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .arrowheadPath{fill:#333333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZsVGxeMSkdAb0RBp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZsVGxeMSkdAb0RBp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZsVGxeMSkdAb0RBp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster text{fill:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp .cluster span{color:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZsVGxeMSkdAb0RBp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZsVGxeMSkdAb0RBp rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZsVGxeMSkdAb0RBp .icon-shape,#mermaid-svg-ZsVGxeMSkdAb0RBp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZsVGxeMSkdAb0RBp .icon-shape p,#mermaid-svg-ZsVGxeMSkdAb0RBp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZsVGxeMSkdAb0RBp .icon-shape .label rect,#mermaid-svg-ZsVGxeMSkdAb0RBp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZsVGxeMSkdAb0RBp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZsVGxeMSkdAb0RBp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZsVGxeMSkdAb0RBp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 意向锁兼容矩阵
兼容
兼容
兼容
兼容
互斥
互斥
意向共享 IS
IS
意向排他 IX
IX
IS
表写锁
表读锁

关键性质(背下来):

组合 是否兼容
IS 和 IS ✅ 兼容
IS 和 IX ✅ 兼容
IX 和 IX ✅ 兼容(所以两个事务各锁不同行不打架)
IS 和 表写锁(X) ❌ 互斥
IX 和 表读锁(S) / 表写锁(X) ❌ 互斥

🔴 面试怎么答 :意向锁之间互不阻塞,所以高并发下行锁(自动带 IX)不会互相堵;它存在的意义是------当有人想 LOCK TABLES t WRITE 加表写锁时,MySQL 扫一眼表上有没有 IX,有就知道"有事务正在锁行,表锁加不上",而不用把每一行的锁都翻出来查。

3.4 AUTO-INC 自增锁

给自增主键插数据时,InnoDB 在表上加一个特殊的表级锁,保证 AUTO_INCREMENT 不重号。由参数 innodb_autoinc_lock_mode 控制:

值 模式 行为 主从(statement)安全
0 传统 整个 INSERT 语句执行完才释放表锁 安全
1 连续(MySQL 5默认) 简单 INSERT 一次分配连续值,批量插入提前知道行数时连续 安全
2 交错(MySQL 8默认) 插入时并发分配,自增可能不连续 不安全

⚠️ MySQL 8 默认 innodb_autoinc_lock_mode=2,配合基于行的 binlog(binlog_format=ROW)是安全的;如果还在用 statement 格式,自增值在主从上可能不一致。这个参数知道就行,一般不用动。


四、行级锁(InnoDB 的精华)

4.1 记录锁 Record Lock

锁索引上的单条记录。分 S 和 X 两种。

🔴 铁律 :行锁加在索引上。如果 WHERE 条件没用到索引,InnoDB 只能全表扫描,给扫到的每一行都加锁------等于锁全表(上一篇实战 4 已复现)。

4.2 间隙锁 Gap Lock

锁索引记录之间的开区间,目的是防止别的事务往间隙里插入新记录(防幻读)。
#mermaid-svg-ZNiHEx5ciUs4POyB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZNiHEx5ciUs4POyB .error-icon{fill:#552222;}#mermaid-svg-ZNiHEx5ciUs4POyB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZNiHEx5ciUs4POyB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZNiHEx5ciUs4POyB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZNiHEx5ciUs4POyB .marker.cross{stroke:#333333;}#mermaid-svg-ZNiHEx5ciUs4POyB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZNiHEx5ciUs4POyB p{margin:0;}#mermaid-svg-ZNiHEx5ciUs4POyB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster-label text{fill:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster-label span{color:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster-label span p{background-color:transparent;}#mermaid-svg-ZNiHEx5ciUs4POyB .label text,#mermaid-svg-ZNiHEx5ciUs4POyB span{fill:#333;color:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB .node rect,#mermaid-svg-ZNiHEx5ciUs4POyB .node circle,#mermaid-svg-ZNiHEx5ciUs4POyB .node ellipse,#mermaid-svg-ZNiHEx5ciUs4POyB .node polygon,#mermaid-svg-ZNiHEx5ciUs4POyB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZNiHEx5ciUs4POyB .rough-node .label text,#mermaid-svg-ZNiHEx5ciUs4POyB .node .label text,#mermaid-svg-ZNiHEx5ciUs4POyB .image-shape .label,#mermaid-svg-ZNiHEx5ciUs4POyB .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZNiHEx5ciUs4POyB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZNiHEx5ciUs4POyB .rough-node .label,#mermaid-svg-ZNiHEx5ciUs4POyB .node .label,#mermaid-svg-ZNiHEx5ciUs4POyB .image-shape .label,#mermaid-svg-ZNiHEx5ciUs4POyB .icon-shape .label{text-align:center;}#mermaid-svg-ZNiHEx5ciUs4POyB .node.clickable{cursor:pointer;}#mermaid-svg-ZNiHEx5ciUs4POyB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZNiHEx5ciUs4POyB .arrowheadPath{fill:#333333;}#mermaid-svg-ZNiHEx5ciUs4POyB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZNiHEx5ciUs4POyB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZNiHEx5ciUs4POyB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZNiHEx5ciUs4POyB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZNiHEx5ciUs4POyB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZNiHEx5ciUs4POyB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster text{fill:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB .cluster span{color:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZNiHEx5ciUs4POyB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZNiHEx5ciUs4POyB rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZNiHEx5ciUs4POyB .icon-shape,#mermaid-svg-ZNiHEx5ciUs4POyB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZNiHEx5ciUs4POyB .icon-shape p,#mermaid-svg-ZNiHEx5ciUs4POyB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZNiHEx5ciUs4POyB .icon-shape .label rect,#mermaid-svg-ZNiHEx5ciUs4POyB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZNiHEx5ciUs4POyB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZNiHEx5ciUs4POyB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZNiHEx5ciUs4POyB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 索引记录上的间隙
(-∞,10]
(10,20]
(20,30]
(30,+∞)

关键性质:

  • 只有 RR 隔离级别有间隙锁(RC 没有,RC 下外键/唯一键检查除外)。
  • 间隙锁之间不互斥 :A 锁了 (20,30),B 也能锁 (20,30),谁都不拦谁。
  • 间隙锁只和"往间隙里 INSERT"的插入意向锁互斥。

4.3 Next-Key Lock

记录锁 + 间隙锁 = Next-Key Lock,锁的是左开右闭区间 (左边记录, 右边记录]。这是 InnoDB 行锁的默认算法。

假设索引上有记录 10、20、30,那么 Next-Key Lock 把整个索引切成:

text 复制代码
(-∞, 10]   (10, 20]   (20, 30]   (30, +∞)

为什么需要它?纯记录锁防不住幻读------你锁了 id=10,别人插个 id=15 你看不见。Next-Key Lock 把 10 和 20 之间的间隙也锁了,别人插不进来。

4.4 插入意向锁 Insert Intention Lock

INSERT 之前,在目标间隙上加的一种特殊间隙锁,表示"我打算在这插一条"。它和间隙锁互斥(间隙锁挡住插入),但插入意向锁之间互相兼容(都想往同一间隙插不同位置,不冲突)。上一篇实战 6 的死锁就是它引起的。


五、加锁基本规则(MySQL 8 InnoDB RR)

这是本篇最硬核的部分。看着一条 SQL,按下表判断它加什么锁:

# 场景 加什么锁
1 任何行锁 都加在索引上;没索引 → 全表所有记录都加锁(表锁效果)
2 唯一索引等值查询,命中记录 退化为记录锁 X,REC_NOT_GAP,不加间隙
3 唯一索引等值查询,未命中 加间隙锁 X,GAP,锁住该值所在间隙
4 普通索引等值查询,命中 Next-Key Lock + 对下一条记录再加间隙锁(普通索引可重复,要防幻读)
5 普通索引等值查询,未命中 加间隙锁
6 范围查询 Next-Key Lock,扫描到不满足条件的第一个值为止(包括界外第一个值的间隙)
7 LIMIT 减少扫描行数,从而减少加锁范围

🔴 规则 2 是高频考点:唯一索引等值命中,Next-Key Lock 退化成记录锁 ------因为唯一索引保证不会再有第二条同样的记录,间隙没必要锁。这就是为什么上一篇实战 1 里 WHERE id=20 FOR UPDATE 是 X,REC_NOT_GAP 而不是 X。

死锁检测

sql 复制代码
-- 默认开
SHOW VARIABLES LIKE 'innodb_deadlock_detect';   -- ON

开启后 InnoDB 用**等待图(wait-for graph)**实时检测有没有环,发现死锁立刻回滚 undo 量最小的事务。高并发下等待图本身有开销,极端场景可以关(但要靠 innodb_lock_wait_timeout 兜底),一般别动。


六、锁兼容矩阵(背这张)

S 读锁 X 写锁 IS IX
S ✅ ❌ ✅ ❌
X ❌ ❌ ❌ ❌
IS ✅ ❌ ✅ ✅
IX ❌ ❌ ✅ ✅

读法:行锁层面 S 和 S 兼容、S/X 互斥、X/X 互斥;意向锁 IS/IX 之间全兼容,只有和真正的表 S/X 锁才互斥。


七、悲观锁 vs 乐观锁

对比 悲观锁 乐观锁
思路 假设一定会冲突,先加锁再操作 假设不冲突,操作时检查版本,冲突了再重试
实现 SELECT ... FOR UPDATE UPDATE t SET val=?, version=version+1 WHERE id=? AND version=?
适合 写多读少、冲突激烈(秒杀扣库存) 读多写少、冲突少(配置更新)
代价 锁等待、死锁风险 更新失败要重试,ABA 问题要处理

乐观锁本质就是数据库里的 CAS(Compare-And-Swap):更新时带上版本号条件, affected rows=0 就说明被人改过了,重试。


八、面试高频(怎么考、怎么答)

  1. MySQL 锁分哪几类? 按粒度(全局/表/行)、按模式(S/X)、按态度(悲观/乐观)、按 InnoDB 实现(记录/间隙/Next-Key/意向/插入意向/AUTO-INC)。
  2. MDL 是什么坑? 长事务持有 MDL 读锁不释放,一个 ALTER 要 MDL 写锁排队,后续所有新查询都被堵死。解决:看长事务、用 pt-osc/gh-ost、ALTER NOWAIT。
  3. 间隙锁和 Next-Key Lock 区别? 间隙锁锁开区间防插入;Next-Key Lock = 记录锁 + 间隙锁,锁左开右闭区间,是 RR 默认行锁算法。
  4. 意向锁解决什么问题? 不用逐行检查,快速判断表锁能不能加;意向锁之间不互斥。
  5. 加锁规则核心? 锁加在索引上;唯一索引等值命中退化为记录锁、未命中加间隙锁;范围查询加 Next-Key Lock 扫到不满足的第一个值。
  6. RC 和 RR 加锁区别? RC 没有间隙锁(并发高、当前读可能幻读),RR 有间隙锁(防幻读但容易死锁)。
  7. 怎么看当前加了什么锁? performance_schema.data_locks(MySQL 8)、data_lock_waits、SHOW ENGINE INNODB STATUS。

总结:你真正需要记住的 6 件事

  1. 锁按粒度分三层:全局锁(FTWRL,备份用,InnoDB 用不到)、表级锁(MDL/意向锁/AUTO-INC)、行级锁(记录/间隙/Next-Key/插入意向)。
  2. MDL 是最隐蔽的坑:长查询 + ALTER = 新查询全堵死;生产大表 DDL 用 pt-osc/gh-ost。
  3. 意向锁 IS/IX 是表级"预告",互相兼容,只和真正的表 S/X 锁互斥,作用是快速判断能否加表锁。
  4. 行锁加在索引上:唯一索引等值命中→记录锁;未命中→间隙锁;范围查询→Next-Key Lock;没索引→锁全表。
  5. 间隙锁只有 RR 有,它之间不互斥、只挡插入,是"不同行也死锁"的根源。
  6. 悲观锁 FOR UPDATE 适合写多冲突多;乐观锁版本号 CAS 适合读多写少。

验证清单

  • 能画出 MySQL 锁的分类树
  • 能说清 MDL 为什么会把库打满,以及三种解决办法
  • 能默写意向锁兼容矩阵(IS/IX 互相兼容,IX 和 S 互斥)
  • 能看着一条 WHERE id=20 FOR UPDATE 说出它加 X,REC_NOT_GAP
  • 能说清为什么唯一索引等值命中不加间隙锁
  • 能解释 RC 和 RR 在间隙锁上的区别
  • 知道用 data_locks 看 LOCK_MODE 四种值

参考资源


下一篇《MySQL 锁(下):锁全表与死锁》:专门啃两个硬骨头------UPDATE 没索引为什么会锁全表(用 Mermaid 画全表扫描加锁过程)、间隙锁到底怎么防幻读;然后把死锁讲透:四个必要条件、五个常见死锁场景、等待图检测、以及七条预防策略。

相关推荐
花间相见1 小时前
【计算基础|MySQL15】MySQL 锁(下):锁全表与死锁
1024程序员节
sdm0704271 小时前
back_insert_iterator-尾插迭代器类
1024程序员节
DisonTangor1 小时前
MOSS-Image-Base 实战:从文生图到 JSON 布局精准控图
人工智能·ai作画·开源·aigc·1024程序员节
TechVoyager_82461 小时前
十六个调试引脚的两副面孔:IT66322 切换芯片的引脚复用与功耗分档
1024程序员节·切换芯片·引脚复用·功耗分档
lunar_coder1 小时前
【C++】如何解决智能指针的析构函数,如何实现shared_ptr的引用计数以及解决shared_ptr的循环引用
c++·1024程序员节
瑞克-桑切斯1 小时前
脚本陷入自循环的解决方法
1024程序员节
Java开发追求者1 小时前
windows快速安装docker
windows·docker·安装·1024程序员节
我叫洋洋1 小时前
用 Qoder(AI 编程助手)把 Cadence 16.6 从零配到能画板
人工智能·单片机·嵌入式硬件·电路·1024程序员节
ai_coder_ai1 小时前
自动化脚本最佳实践
1024程序员节