﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-JAVA-文章分类-数据库</title><link>http://www.blogjava.net/zzzlyr/category/55381.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 12 Jul 2021 07:52:19 GMT</lastBuildDate><pubDate>Mon, 12 Jul 2021 07:52:19 GMT</pubDate><ttl>60</ttl><item><title>InnoDB的锁的机制探究</title><link>http://www.blogjava.net/zzzlyr/articles/435921.html</link><dc:creator>为自己代言</dc:creator><author>为自己代言</author><pubDate>Mon, 12 Jul 2021 07:38:00 GMT</pubDate><guid>http://www.blogjava.net/zzzlyr/articles/435921.html</guid><wfw:comment>http://www.blogjava.net/zzzlyr/comments/435921.html</wfw:comment><comments>http://www.blogjava.net/zzzlyr/articles/435921.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zzzlyr/comments/commentRss/435921.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zzzlyr/services/trackbacks/435921.html</trackback:ping><description><![CDATA[<h1>InnoDB的锁</h1>
<h2>InnoDB的行锁：共享锁、排他锁、MDL锁</h2>
<h5>共享锁：又称读锁、S锁。一个事务获取一个数据行的共享锁，其他事务能获取该行对应的共享锁，但不能获得排他锁；即一个事务在读取一个数据行时，其他事务也可以读，但不能对数据进行增删改查。</h5>
<p>应用：</p>
<p>1.自动提交模式下的select查询，不加任何锁，直接返回查询结果</p>
<p>2.通过select&#8230;&#8230;lock in share mode在被读取的行记录或范围上加一个读锁，其他事务可以读，但是申请加写锁会被阻塞</p>
<h5>排他锁：又称写锁、X锁。一个事务获取了一个数据行的写锁，其他事务就不能再获取该行的其他锁，写锁优先级最高。</h5>
<p>应用：</p>
<p>1.一些DML操作会对行记录加写锁</p>
<p>2.select for update会对读取的行记录上加一个写锁，其他任何事务都不能对锁定的行加任何锁，否则会被阻塞</p>
<h5>MDL锁：MySQL5.5引入，用于保证表中元数据的信息。在会话A中，表开启了查询事务后，会自动获得一个MDL锁，会话B就不能执行任何DDL语句的操作</h5>
<h3>行锁实现方式</h3>
<p>InnoDB 行锁是通过给索引上的索引项加锁来实现的，这一点 MySQL 与 Oracle 不同，后者是 
通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过 索引条件检索数据，InnoDB 
才使用行级锁，否则，InnoDB 将使用表锁! 在实际应用中，要特别注意 InnoDB 行锁的这一特性，不然的话，可能导致大量的锁冲突， 
从而影响并发性能。</p>
<h3>行锁的三种算法</h3>
<p>InnoDB 存储引擎有三种行锁的算法，其分别是：</p>
<ul><li>Record Lock: 单个行记录上的锁</li><li>Gap Lock: 间隙锁，锁定一个范围，但不包含记录本身</li><li>Next-Key 锁: Gap Lock + Record Lock，锁定一个范围，并且会锁定记录本身</li></ul>
<p>RC模式下只采用Record Lock，RR模式下采用了Next-Key</p>
<h3>加锁场景分析</h3>
<ul><li>主键索引</li></ul>
<p>如果我们加锁的行上存在主键索引，那么就会在这个主键索引上添加一个 Record Lock。</p>
<ul><li>辅助索引</li></ul>
<p>如果我们加锁的行上存在辅助索引，那么我们就会在这行的辅助索引上添加 Next-Key Lock，并在这行之后的辅助索引上添加一个 Gap Lock</p>
<p>辅助索引上的 Next-Key Lock 和 Gap Lock 都是针对 Repeatable Read 隔离模式存在的，这两种锁都是为了防止幻读现象的发生。</p>
<ul><li>唯一的辅助索引</li></ul>
<p>这里有一个特殊情况，如果辅助索引是唯一索引的话，MySQL 会将 Next-Key Lock 降级为 Record Lock，只会锁定当前记录的辅助索引。</p>
<p>如果唯一索引由多个列组成的，而我们只锁定其中一个列的话，那么此时并不会进行锁降级，还会添加 Next-Key Lock 和 Gap Lock。</p>
<ul><li>Insert 语句</li></ul>
<p>在 InnoDB 存储引擎中，对于 Insert 的操作，其会检查插入记录的下一条记录是否被锁定，若已经被锁定，则不允许查询。</p>
<h2>意向锁</h2>
<p>意向锁可以分为意向共享锁(Intention Shared Lock, IS)和意向排他锁(Intention eXclusive 
Lock, 
IX)。但它的锁定方式和共享锁和排他锁并不相同，意向锁上锁只是表示一种&#8220;意向&#8221;,并不会真的将对象锁住，让其他事物无法修改或访问。例如事物T1想要修改表<code>test</code>中的行<code>r1</code>，它会上两个锁:</p>
<ol><li>在表<code>test</code>上意向排他锁</li><li>在行<code>r1</code>上排他锁</li></ol>
<p>事物T1在<code>test</code>表上上了意向排他锁，并不代表其他事物无法访问<code>test</code>了，它上的锁只是表明一种意向，它将会在<code>db</code>中的<code>test</code>表中的某几行记录上上一个排他锁。</p>
<table>
<thead>
<tr>
<th><br /></th>
<th>意向共享锁</th>
<th>意向排他锁</th>
<th>共享锁</th>
<th>排他锁</th>
</tr>
</thead>
<tbody>
<tr>
<td>意向共享锁</td>
<td>兼容</td>
<td>兼容</td>
<td>兼容</td>
<td>不兼容</td>
</tr>
<tr>
<td>意向排他锁</td>
<td>兼容</td>
<td>兼容</td>
<td>不兼容</td>
<td>不兼容</td>
</tr>
<tr>
<td>共享锁</td>
<td>兼容</td>
<td>不兼容</td>
<td>兼容</td>
<td>不兼容</td>
</tr>
<tr>
<td>排他锁</td>
<td>不兼容</td>
<td>不兼容</td>
<td>不兼容</td>
<td>不兼容</td>
</tr>
</tbody>
</table>
<h2>一致性非锁定读</h2>
<p>一致性非锁定读是指 InnoDB 存储引擎通过行多版本控制(multi 
version)的方式来读取当前执行时间数据库中行的数据。具体来说就是如果一个事务读取的行正在被锁定，那么它就会去读取这行数据之前的快照数据，而不会等待这行数据上的锁释放。这个读取流程如图1所示:</p>
<p><img src="https://passage-1253400711.cos.ap-beijing.myqcloud.com/2018-03-06-052802.png" alt="图1" /></p>
<p>行的快照数据是通过undo段来实现的，而undo段用来回滚事务，所以快照数据本身没有额外的开销。此外，读取快照数据时不需要上锁的，因为没有事务会对快照数据进行更改。</p>
<p>MySQL 中并不是每种隔离级别都采用非一致性非锁定读的读取模式，而且就算是采用了一致性非锁定读，不同隔离级别的表现也不相同。在 READ
 COMMITTED 和 REPEATABLE READ 这两种隔离级别下，InnoDB存储引擎都使用一致性非锁定读。但是对于快照数据，READ
 COMMITTED 隔离模式中的事务读取的是当前行最新的快照数据，而 REPEATABLE READ 
隔离模式中的事务读取的是事务开始时的行数据版本。</p>
<h2>一致性锁定读</h2>
<p>在 InnoDB 存储引擎中，<code>select</code>语句默认采取的是一致性非锁定读的情况，但是有时候我们也有需求需要对某一行记录进行锁定再来读取，这就是一致性锁定读。</p>
<p>InnoDB 对于<code>select</code>语句支持以下两种锁定读:</p>
<ul><li><code>select ... for update</code></li><li><code>select ... lock in share mode</code></li></ul>
<p><code>select ... for update</code>会对读取的记录加一个X锁，其他事务不能够再来为这些记录加锁。<code>select ... lock in share mode</code>会对读取的记录加一个S锁，其它事务能够再为这些记录加一个S锁，但不能加X锁。</p>
<p>对于一致性非锁定读，即使行记录上加了X锁，它也是能够读取的，因为它读取的是行记录的快照数据，并没有读取行记录本身。</p>
<p><code>select ... for update</code>和<code>select ... lock in share mode</code>这两个语句必须在一个事务中，当事务提交了，锁也就释放了。因此在使用这两条语句之前必须先执行<code>begin</code>, <code>start transaction</code>，或者执行<code>set autocommit = 0</code>。</p>
<h2>InnoDB 在不同隔离级别下的一致性读及锁的差异</h2>
<pre><code class="lang-">consisten read //一致性读
share locks //共享锁
Exclusive locks //排他锁
</code></pre>
<table>
<thead>
<tr>
<th><br /></th>
<th><br /></th>
<th>读未提交</th>
<th>读已提交</th>
<th>可重复读</th>
<th>串行化</th>
</tr>
</thead>
<tbody>
<tr>
<td>SQL</td>
<td>条件</td>
<td><br /></td>
<td><br /></td>
<td><br /></td>
<td><br /></td>
</tr>
<tr>
<td>select</td>
<td>相等</td>
<td>None locks</td>
<td>Consisten read/None lock</td>
<td>Consisten read/None lock</td>
<td>Share locks</td>
</tr>
<tr>
<td><br /></td>
<td>范围</td>
<td>None locks</td>
<td>Consisten read/None lock</td>
<td>Consisten read/None lock</td>
<td>Share Next-Key</td>
</tr>
<tr>
<td>update</td>
<td>相等</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
</tr>
<tr>
<td><br /></td>
<td>范围</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
</tr>
<tr>
<td>Insert</td>
<td>N/A</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
</tr>
<tr>
<td>Replace</td>
<td>无键冲突</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
</tr>
<tr>
<td><br /></td>
<td>键冲突</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
</tr>
<tr>
<td>delete</td>
<td>相等</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
</tr>
<tr>
<td><br /></td>
<td>范围</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
</tr>
<tr>
<td><strong>Select &#8230; from &#8230; Lock in share mode</strong></td>
<td>相等</td>
<td>Share locks</td>
<td>Share locks</td>
<td>Share locks</td>
<td>Share locks</td>
</tr>
<tr>
<td><br /></td>
<td>范围</td>
<td>Share locks</td>
<td>Share locks</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
</tr>
<tr>
<td><strong>Select * from &#8230; For update</strong></td>
<td>相等</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
</tr>
<tr>
<td><br /></td>
<td>范围</td>
<td>Exclusive locks</td>
<td>Exclusive locks</td>
<td>Exclusive next-key</td>
<td>Exclusive next-key</td>
</tr>
<tr>
<td><strong>Insert into &#8230; Select &#8230;</strong></td>
<td><strong>innodb_locks_ unsafe_for_bi nlog=off</strong></td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
</tr>
<tr>
<td>(指源表锁)</td>
<td><strong>innodb_locks_ unsafe_for_bi nlog=on</strong></td>
<td>None locks</td>
<td>Consisten read/None lock</td>
<td>Consisten read/None lock</td>
<td>Share Next-Key</td>
</tr>
<tr>
<td><strong>create table &#8230; Select &#8230;</strong></td>
<td><strong>innodb_locks_ unsafe_for_bi nlog=off</strong></td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
<td>Share Next-Key</td>
</tr>
<tr>
<td>(指源表锁)</td>
<td><strong>innodb_locks_ unsafe_for_bi nlog=on</strong></td>
<td>None locks</td>
<td>Consisten read/None lock</td>
<td>Consisten read/None lock</td>
<td>Share Next-Key</td>
</tr>
</tbody>
</table>
<h5>在了解 InnoDB 锁特性后，用户可以通过设计和 SQL 调整等措施减少锁冲突和死锁，包括:</h5>
<ul><li>尽量使用较低的隔离级别;</li><li>精心设计索引，并尽量使用索引访问数据，使加锁更精确，从而减少锁冲突的机会;</li><li>选择合理的事务大小， 小事务发生锁冲突的几率也更小;</li><li>给记录集显示加锁时，最好一次性请求足够级别的锁。比如要修改数据的话，最好直接申请排他锁，而不是先申请共享锁，修改时再请求排他锁，这样容易产生死锁;</li><li>不同的程序访问一组表时，应尽量约定以相同的顺序访问各表，对一个表而言，尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;</li><li>尽量用相等条件访问数据，这样可以避免间隙锁对并发插入的影响;</li><li>不要申请超过实际需要的锁级别;除非必须，查询时不要显示加锁;</li><li>对于一些特定的事务，可以使用表锁来提高处理速度或减少死锁的可能。</li></ul>
<h2>参考资料</h2>
<p>1.https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-intention-locks&nbsp; mysql官网开发手册<br /></p>
<p>2.《MySQL 技术内幕 &#8211; InnoDB 存储引擎》</p>
<p>3.《深入浅出MySQL》</p>
<p>4.https://www.modb.pro/db/33873</p><p><br /></p><img src ="http://www.blogjava.net/zzzlyr/aggbug/435921.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zzzlyr/" target="_blank">为自己代言</a> 2021-07-12 15:38 <a href="http://www.blogjava.net/zzzlyr/articles/435921.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>