飛天少侠 發表於 2025-12-26 09:42:13

MySQL InnoDB锁机制之从Record到Next-Key的使用解读

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、为什么 InnoDB 的锁机制这么复杂?</a></li><li><a href="#_label1">二、InnoDB 的三种核心锁(理解它们的作用和触发条件)</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">Record Lock:真实行上的锁</a></li><li><a href="#_lab2_1_1">Gap Lock:只锁&ldquo;间隙&rdquo;,不锁数据</a></li><li><a href="#_lab2_1_2">Next-Key Lock:Record + Gap 的组合锁</a></li></ul><li><a href="#_label2">三、锁到底由哪些 SQL 触发?</a></li><ul class="second_class_ul"></ul><li><a href="#_label3">四、锁具体加在什么区间?</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_3">WHERE id = 20 FOR UPDATE</a></li><li><a href="#_lab2_3_4">WHERE id &gt; 20 FOR UPDATE</a></li><li><a href="#_lab2_3_5">WHERE id BETWEEN 15 AND 35 FOR UPDATE</a></li><li><a href="#_lab2_3_6">无索引条件</a></li></ul><li><a href="#_label4">五、总结</a></li><ul class="second_class_ul"></ul></ul></div><p>在真实业务中,你遇到的大多数 MySQL 性能问题、死锁问题,几乎都与&ldquo;锁&rdquo;有关。但很多工程师对锁的理解停留在碎片层面:</p>
<p>知道&ldquo;行锁&rdquo;&ldquo;间隙锁&rdquo;&ldquo;next-key-lock&rdquo;,但不知道 <strong>SQL 是如何触发这些锁的、锁到底锁在哪里、为什么会锁这么多</strong>。</p>
<p>这一篇文章,我会用工程化思维,带你一次性理解 <strong>InnoDB 锁机制的全景图</strong>。</p>
<p>看完以后,你将具备:</p>
<ul><li>✔ 能看懂锁的真实作用</li><li>✔ 能从 SQL 推断出锁范围</li><li>✔ 能解释死锁发生原因</li><li>✔ 能在面试中条理清晰地讲出锁机制</li></ul>
<p class="maodian"><a name="_label0"></a></p><h2>一、为什么 InnoDB 的锁机制这么复杂?</h2>
<p>MySQL 采用 <strong>MVCC + 锁</strong> 实现事务隔离,其中最关键的隔离级别是:</p>
<ul><li><strong>RC</strong>:读已提交</li><li><strong>RR</strong>:可重复读(默认)</li></ul>
<p>RR 是企业最常见的隔离级别,它要解决&ldquo;幻读&rdquo;问题。</p>
<p>于是有了三个锁:</p>
<ul><li><strong>记录锁(Record Lock)</strong></li><li><strong>间隙锁(Gap Lock)</strong></li><li><strong>Next-Key Lock(Record + Gap)</strong></li></ul>
<p>所有复杂问题都来自这个组合。</p>
<p class="maodian"><a name="_label1"></a></p><h2>二、InnoDB 的三种核心锁(理解它们的作用和触发条件)</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>Record Lock:真实行上的锁</h3>
<ul><li>锁的对象:<strong>一条真实存在的记录</strong></li><li>触发场景:精确命中唯一索引</li></ul>
<p>例如:</p>
<div class="jb51code"><pre class="brush:sql;">SELECT * FROM user WHERE id = 10 FOR UPDATE;</pre></div>
<p>只锁 <code>(10]</code> &mdash;&mdash; 单条记录。</p>
<p><strong>特点:不会锁间隙,因此不会阻止插入。</strong></p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>Gap Lock:只锁&ldquo;间隙&rdquo;,不锁数据</h3>
<p>作用:阻止&ldquo;间隙内插入新数据&rdquo;,防止幻读。</p>
<p>例如:</p>
<p>索引中已有值:</p>
<div class="jb51code"><pre class="brush:sql;">10 --- 20 --- 30</pre></div>
<p>SQL:</p>
<div class="jb51code"><pre class="brush:sql;">SELECT * FROM user WHERE age &gt; 20 FOR UPDATE;</pre></div>
<p>Gap Lock 会锁住:</p>
<div class="jb51code"><pre class="brush:sql;">(20, 30)
(30, +∞)</pre></div>
<p><strong>重点:Gap Lock 不锁记录,只锁区间。</strong></p>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>Next-Key Lock:Record + Gap 的组合锁</h3>
<p>RR 下范围查询的默认锁模式:</p>
<div class="jb51code"><pre class="brush:sql;">(prev_key, record_key]</pre></div>
<p>例如:(假设索引有 10、20、30)</p>
<div class="jb51code"><pre class="brush:sql;">SELECT * FROM t WHERE age BETWEEN 15 AND 25 FOR UPDATE;</pre></div>
<p>锁住的区间:</p>
<div class="jb51code"><pre class="brush:sql;">(10,20]
(20,30]</pre></div>
<p>作用:</p>
<ul><li>✔ 锁住命中的记录</li><li>✔ 锁住记录前的 gap &rarr; 阻止插入</li></ul>
<p>这就是为什么 RR 隔离级别能规避幻读。</p>
<p class="maodian"><a name="_label2"></a></p><h2>三、锁到底由哪些 SQL 触发?</h2>
<p>&ldquo; SQL &rarr; 锁类型&rdquo; 映射表:</p>
<table><thead><tr><th>SQL 场景</th><th>索引情况</th><th>锁类型</th><th>原因</th></tr></thead><tbody><tr><td>WHERE id = ?(唯一键)</td><td>精确命中</td><td>Record Lock</td><td>不需要锁 gap</td></tr><tr><td>WHERE id = ?(普通索引)</td><td>精确匹配,但非唯一</td><td>Next-Key Lock</td><td>防止幻读</td></tr><tr><td>WHERE age &gt; ? / &lt; ?</td><td>范围扫描</td><td>Next-Key Lock</td><td>必须锁 gap</td></tr><tr><td>BETWEEN 范围查询</td><td>范围扫描</td><td>Next-Key Lock</td><td>防止插入</td></tr><tr><td>无索引过滤</td><td>全表扫描</td><td>大量 Record Lock</td><td>每条记录都会被锁</td></tr><tr><td>LIKE &#39;%abc&#39;</td><td>无法走索引</td><td>表锁风险</td><td>全表扫描</td></tr></tbody></table>
<p>一句话总结:</p>
<ul><li><strong>能精确锁住记录 &rarr; Record Lock</strong></li><li><strong>需要范围扫描 &rarr; Next-Key Lock</strong></li><li><strong>范围扫描一定会锁 gap &rarr; Gap Lock</strong></li></ul>
<p class="maodian"><a name="_label3"></a></p><h2>四、锁具体加在什么区间?</h2>
<p>假设索引中有如下值:</p>
<div class="jb51code"><pre class="brush:sql;">10 ---- 20 ---- 30 ---- 40</pre></div>
<p>来看不同 SQL 加的锁👇</p>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>WHERE id = 20 FOR UPDATE</h3>
<p>锁:</p>
<div class="jb51code"><pre class="brush:sql;">(10, 20]</pre></div>
<p>但如果字段是主键/唯一键,会优化成:</p>
<div class="jb51code"><pre class="brush:sql;"></pre></div>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>WHERE id &gt; 20 FOR UPDATE</h3>
<p>锁:</p>
<div class="jb51code"><pre class="brush:sql;">(20,30)
(30,40)
(40,+∞)</pre></div>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>WHERE id BETWEEN 15 AND 35 FOR UPDATE</h3>
<p>锁:</p>
<div class="jb51code"><pre class="brush:sql;">(10,20]
(20,30]
(30,40]</pre></div>
<p class="maodian"><a name="_lab2_3_6"></a></p><h3>无索引条件</h3>
<div class="jb51code"><pre class="brush:sql;">SELECT * FROM user WHERE name='xxx' FOR UPDATE;</pre></div>
<p>锁住所有记录:</p>
<div class="jb51code"><pre class="brush:sql;">, , , </pre></div>
<p>&rarr; 大量锁冲突发生的根源。</p>
<p class="maodian"><a name="_label4"></a></p><h2>五、总结</h2>
<ul><li><strong>InnoDB 的锁永远基于索引。</strong></li><li><strong>无法精确匹配记录,就会使用 Next-Key Lock。</strong></li><li><strong>范围查询一定会带 gap 锁。</strong></li></ul>
<p>掌握这三点后,死锁、锁等待、幻读问题都能一眼看穿。</p>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
頁: [1]
查看完整版本: MySQL InnoDB锁机制之从Record到Next-Key的使用解读