夏雯 發表於 2025-12-30 15:00:38

MySql InnoDB引擎下的锁使用及说明

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">MySql锁种类</a></li><li><a href="#_label1">Shared and Exclusive Locks</a></li><li><a href="#_label2">Intention Locks</a></li><li><a href="#_label3">Record Locks</a></li><li><a href="#_label4">Gap Locks(为了防止幻读)</a></li><li><a href="#_label5">Next-Key Locks</a></li><li><a href="#_label6">Insert Intention Locks</a></li><li><a href="#_label7">AUTO-INC Locks</a></li><li><a href="#_label8">Predicate Locks for Spatial Indexes</a></li><li><a href="#_label9">总结</a></li></ul></div><p class="maodian"><a name="_label0"></a></p><h2>MySql锁种类</h2>
<p>在innodb中,锁的种类如下:</p>
<ul><li>Shared and Exclusive Locks(共享锁和排他锁)</li><li>Intention Locks(意向锁)</li><li>Record Locks(记录锁)</li><li>Gap Locks(间隙锁)</li><li>Next-key Locks(Gap Lock + Record Lock)</li><li>Insert Intention Locks(插入意向锁)</li><li>AUTO-INC Locks(自增锁)</li><li>Predicate Locks for Spatial Indexes</li></ul>
<p class="maodian"><a name="_label1"></a></p><h2>Shared and Exclusive Locks</h2>
<p>Innodb实现了标准的行级锁,其中有两种类型:</p>
<ul><li>共享锁:允许持有共享锁的事务读取该行记录。</li><li>排他锁:允许持有排他锁的事务更新或者删除该行记录。</li></ul>
<p>如果事务T1持有某行数据的共享锁,那么另一个事务T1想获取该行数据的锁时:</p>
<ul><li>共享锁:T2会马上获得一个共享锁,此时T1、T2都有该行数据的共享锁。</li><li>排他锁:T2不能马上获得排他锁。</li></ul>
<p>如果事务T1持有某行数据的排他锁,那么其他事务都不能立刻获得该行数据的共享锁或排他锁。其他事务需要等待T1释放它所持有的该行数据的排他锁。</p>
<p class="maodian"><a name="_label2"></a></p><h2>Intention Locks</h2>
<p>Innodb支持多粒度锁,允许行锁和表锁共存。为了实现多粒度级别的锁,innodb使用了意图锁。意图锁是表级别的锁,它指示事务稍后需要表中的一行使用哪种类型的锁(共享或排他)。</p>
<p><strong>以下是两种类型的意向锁:</strong></p>
<ul><li>共享意向锁:表明该事务计划对某行数据设置一个共享锁。</li><li>排他意向锁:表明该事务计划对某行数据设置一个排他锁。</li></ul>
<p>例如,select .... for share就设置了一个共享意向锁,select .... for update就设置了一个排他意向锁。</p>
<p><strong>意向锁的形为:</strong></p>
<ul><li>在一个事务获取到某行锁的共享锁之前,它必须先获得一个共享意向锁或者更高级别的锁(也就是排他意向锁啦)。</li><li>在一个事务获取到某行锁的排他锁之前,它必须先获得一个排他意向锁。</li></ul>
<p>表级锁类型的兼容性如下:</p>
<table><tbody><tr><td></td><td><p>排他锁</p></td><td><p>排他意向锁</p></td><td><p>共享锁</p></td><td><p>共享意向锁</p></td></tr><tr><td><p>排他锁</p></td><td><p>冲突</p></td><td><p>冲突</p></td><td><p>冲突</p></td><td><p>冲突</p></td></tr><tr><td><p>排他意向锁</p></td><td><p>冲突</p></td><td><p>兼容</p></td><td><p>冲突</p></td><td><p>兼容</p></td></tr><tr><td><p>共享锁</p></td><td><p>冲突</p></td><td><p>冲突</p></td><td><p>兼容</p></td><td><p>兼容</p></td></tr><tr><td><p>共享意向锁</p></td><td><p>冲突</p></td><td><p>兼容</p></td><td><p>兼容</p></td><td><p>兼容</p></td></tr></tbody></table>
<p>事务可以获得与已经存在的锁兼容的锁,不能获得与已经存在的锁冲突的锁。在有冲突的锁被释放之前,事务会一直等待。如果一个锁的请求与已存在的锁冲突并且因为有可能产生死锁而不能被获得,那么就会产生一个错误。</p>
<p>除了全表请求(如,lock table ... write)之外,意向锁不会阻塞任何事物。意向锁的主要目的就是表面有人正在对某行进行加锁,或者正要锁住表内某行。</p>
<p class="maodian"><a name="_label3"></a></p><h2>Record Locks</h2>
<p>记录锁是对索引进行记录的锁。例如,select c1 from t where c1 = 10 for update 禁止了任何其他事务对c1 = 10的数据行进行插入,更新或者删除。</p>
<p>记录锁总是对索引进行加锁,即使该表没有定义索引。如果表没有自己定义索引,那么innodb就会定义一个隐式的聚集索引(hidden clustered index)并且对这个索引进行加锁。</p>
<p class="maodian"><a name="_label4"></a></p><h2>Gap Locks(为了防止幻读)</h2>
<p>间隙锁是对一个索引间隙加锁的锁。如果索引间隙的范围为(index1,index2),那么就锁住index1到index2之间的间隙,如果索引只有一个index,那么根据innoddb底层的B+树的结构,假设index前面最近的一个索引为index1,后面最近一个索引为index2,那么就会锁住index1到index2之间的间隙。</p>
<p>例如,select c1 from t where c1 between 10 and 20 for update 禁止了其他任何事物插入一行c1=15的数据行,无论是不是已经有了一行c1=15的数据行,因为10-20这个间隙的值都被锁上了间隙锁。间隙可能会包含单个索引值或者多个索引值,甚至有可能是空的。</p>
<p>再如,假设表的数据如下:</p>
<div class="jb51code"><pre class="brush:sql;">SELECT * FROM t;
+------+
| age |
+------+
| 21 |
| 25 |
| 30 |
+------+</pre></div>
<p>那么,当执行select age from t where age = 25 for updata时,锁住的就是21-30之间的间隙。</p>
<p>间隙锁是在性能和并发之间权衡的一部分,在某些事务隔离级别会使用,在某些事务隔离级别不使用。</p>
<p>对于使用唯一性索引搜索唯一行的情况,不需要加上间隙锁(这不包括使用联合唯一索引而只搜索其中一部分的列数据的情况,在这种情况下确实会加上一个间隙锁)。</p>
<p>例如,select * from t where id = 100 只会对id值为100的行使用一个记录锁(record lock),不管其他事务是否会在前面的间隙插入行。但如果id上没有一个唯一索引,那么就会锁住某个间隙。</p>
<p>innodb中的间隙锁是&ldquo;纯粹抑制的&rdquo;(purely inhibitive),这意味着他们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存,一个事务使用间隙锁并不会阻止另一个事务使用同一间隙上的间隙锁。</p>
<p>可以通过将事务隔离级别设置为READ COMMITTED来禁用间隙锁。在READ COMMITTED隔离级别下,间隙锁在搜索和索引扫描中被禁用,只用于外键约束检查和重复键检查。</p>
<p class="maodian"><a name="_label5"></a></p><h2>Next-Key Locks</h2>
<p>Next-Key Lock是索引上的记录锁和锁住该索引前面的间隙的间隙锁的组合。</p>
<p>innodb通过对搜索和扫描索引时得到的索引加共享锁或排他锁来实现行级锁。因此,行级锁实际上是索引记录锁。索引记录上的next-key锁也会影响该索引记录之前的间隙。也就是说,next-key锁是由索引记录锁加上对索引之前的间隙加锁的间隙锁组成的。如果一个会话对索引中的记录R有一个共享锁或排他锁,那么另一个会话不能再紧挨着索引顺序的R之前的间隙插入一个新的索引记录。</p>
<p>默认情况下,innodb引擎采取REPEATABLE READ事务隔离级别,此时,innodb使用next-key lock来防止幻读。</p>
<p class="maodian"><a name="_label6"></a></p><h2>Insert Intention Locks</h2>
<p>插入意向锁时一种由insert操作在行插入之前的间隙锁类型。这个锁表面了插入的意图,如果插入到同一个索引间隙中的多个事务没有插入到间隙中的相同位置,那么它们就不需要互相等待。假设有值为4和7的索引,有两个事务分别像插入5和6,那么他们在插入数据之前都会锁住4和7之间的间隙,但此时他们不会互相阻塞,而他们随后会获得5和6的排他锁,因为是行锁,所有依旧不会互相阻塞。</p>
<p>例如:</p>
<p>客户端A创建一个包含两个索引记录(90和102)的表,然后启动一个事务,对ID大于100的索引记录获取排他锁。排他锁包括记录102之前的间隙锁::</p>
<div class="jb51code"><pre class="brush:sql;">mysql&gt; CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql&gt; INSERT INTO child (id) values (90),(102);

mysql&gt; START TRANSACTION;
mysql&gt; SELECT * FROM child WHERE id &gt; 100 FOR UPDATE;
+-----+
| id|
+-----+
| 102 |
+-----+</pre></div>
<p>客户端B开始一个事务,向间隙中插入一条记录。事务在等待获得排他锁时接受一个插入意图锁:</p>
<div class="jb51code"><pre class="brush:sql;">mysql&gt; START TRANSACTION;
mysql&gt; INSERT INTO child (id) VALUES (101);</pre></div>
<p>在客户端A的事务提交之前,客户端B的对应间隙的插入都不能被执行。</p>
<p class="maodian"><a name="_label7"></a></p><h2>AUTO-INC Locks</h2>
<p>AUTO-INC是一种特殊的表锁,当事务想插入一条自增行的时候使用。</p>
<p>例如,如果一个事务正在插入数据,任何其他事务如果也要插入,那么他们都必须等待当前事务插入完毕,这样才能保证那个自增字段是连续的。</p>
<p class="maodian"><a name="_label8"></a></p><h2>Predicate Locks for Spatial Indexes</h2>
<p>InnoDB支持包含空间数据的列的空间索引。为了处理想要锁住空间索引的锁,next-key锁无法实现REPEATABLE READ 或 SERIALIZABLE事务隔离级别的要求。多维数据没有绝对的排序概念,因此不知道哪一个是下一个索引,也就无法获得间隙锁。</p>
<p>为了支持具有空间索引的表的隔离级别,InnoDB使用Predicate Locks。一个Predicate锁包含每个维度的边界,每次就锁住这些边界就可以了。</p>
<p class="maodian"><a name="_label9"></a></p><h2>总结</h2>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
頁: [1]
查看完整版本: MySql InnoDB引擎下的锁使用及说明