転送:https://www.aneasystone.com/archives/2017/12/solving-dead-locks-three.html
このブログでは、我々は通常、これらの実装を見て分析をロックするいくつかの一般的なSQL文は、任意のSQLロックを追加する必要があります。唯一の我々は十分にデッドロックの問題に直面して立ち上げ注ぐことが知られている手順をロックするSQL文を記述問題がロックによって引き起こされるものです。前回のブログでは、我々はすでにロックモードの異なる種類について学び、MySQLをロックダウンしている、我々はおなじみのロックされている彼らの互換性マトリックス、に特別な注意を払う必要があり、これらのロックと互換性がありません、互換性はありませんが、多くの場合、デッドロックにつながる犯人です。全体として、ロック粒度中のMySQLは、2つに分けることができるテーブルと行ロック、表ロックは、次のとおりテーブル・レベルの読み取りロック、テーブル・レベルの書き込みロック、ロックの意図を読み取り、ロックを記述する意図、セルフロックを増大;ロックラインです。レコードロックを読み取る、書き込みロック、ロックギャップ、ネクストキーロック、意図ロック・インサートを記録します。驚くことではないが、デッドロックの問題の大半は原因これらのロックの競合によって引き起こされます。
我々は、異なる分離レベルのロックが同じではないことを知っている、例えば、ギャップロックとRCの分離レベルではないRR分離レベルの下でネクストキーロックは、(例外がある)があるので、SQL分析のためにロックされ、あなたは、データベースの分離レベルを知ってもらう必要があります。RRとRCは、より多く使用するので、分析のために、このブログは、これらの2つの分離レベルので。
このブログシリーズの「デッドロックを解決するための道」で、あなたはまた、いくつかの他のを読むことができます:
- 学習トランザクション分離レベル
- ロックの一般的な種類について学びます
- ロックマスター共通の分析SQL文
- デッドロックの問題を分析し、解決します
まず、基本的なロックルール
MySQLのロックの様々な、いくつかの基本原則など、同じロックを残るものの:スナップショットの読み取りがロックされていない、更新ステートメントは間違いなくプラス排他ロックで、RCの分離レベルにはギャップロックではありませんように。これらのルールは以下のように、それは後で紹介繰り返すことはしません要約されます。
-
一般的な声明をロック
- 通常の状況下では読みSELECT ...文では、スナップショット、ロックされていません。
- SELECT ... SHAREモード現在のREAD文、プラスSロックをロック。
- 現在の読書、プラスXロックのためのSELECT ... FOR UPDATE文。
- 現在の読書のため、プラスXロック(例えばINSERT、DELETE、UPDATEなど)の一般的なDML文。
- (など、CREATE、ALTER、など)の一般的なDDL文に加えて、テーブルレベルロック、およびステートメントがこれらをロールバックすることはできませんコミット暗黙的です。
-
テーブルロック
- 表ロック(ロック点SとXロック)
- 意図ロック(サブISによってIX)
- (のみinnodb_autoinc_lock_mode = 0またはバルクインサートがあってもよい場合は、一般的に参照)セルフロックを増やし
-
行ロック
- レコードのロック(ロック点SとXロック)
- ギャップロック(ロック点SとXロック)
- ネクストキーロック(ロック点SとXロック)
- 意図的ロックを挿入
-
行ロック分析
- 行ロックは、インデックスに追加され、最終的にクラスタ化インデックスの上に落下しています。
- レコードを追加する処理である行ロックを追加します。
-
ロック競合
- S Sロックと互換性ロック、X及びXロックが競合、X Sロックとロック競合のロック。
- 前回のブログでは、表と行ロックの競合マトリックスを参照してください ロックの一般的な種類について学びます。
-
異なる分離レベルでロック
- しかし、現在のSerializableの分離レベルでの読み取りに加え、Sロック;これは、通常の状況下で読みSELECT ...文がロックされていない、スナップショットであると言います。
- 隙間およびネクストキーロックラッチ(が存在することになる特殊なケース:パージ+ユニークキー)が存在しない分離レベルRC下で、
- 異なる分離レベルでの違いをロックし、以前のブログを参照 学習トランザクション分離レベルを、
ロック分析第二に、単純なSQL
それは彼のブログでDengchengの前任者「MySQLのロック処理および分析」いくつかの一般的なSQLのロックで、このブログの詳細な分析を実施し、オンラインでのMySQLは、モデルのロックの分析を紹介すると言うことができる、ロック分析ほぼすべてのオンラインブログはこのブログの調査への参照である古典的には、強くお勧めします、と述べました。私がここに持っても例外ではありませんが、いくつかの並べ替えを持っていたし、彼の基礎をまとめます。
我々は2つの非一意のインデックス、スコア(クレジット)がないインデックス用の2つの一意のインデックス、名前(名)、年齢(歳)のために、学生の一例として、どこID主キーなし(学生証)を次の表を参照して、これを使用しています。
このセクションでは、SQLの最も簡単な種類を分析し、それはWHERE条件、同等の範囲のクエリまたはクエリが含まれています。SQLは非常に単純な、しかし、列の異なるタイプのですが、我々はまだ、さまざまな状況に直面するだろう。
- クラスタード・インデックス、クエリヒット:UPDATEの学生は、スコア= 100 ID = 15を設定します。
- クラスタード・インデックス、クエリミス:UPDATEの学生は、スコア= 100 ID = 16を設定します。
- 2つのユニークなインデックス、クエリヒット:UPDATEの学生は、スコア= 100なし=「S0003」を設定。
- 2つのだけインデックス、クエリミス:UPDATEの学生は、スコア= 100なし=「S0008」を設定。
- 二つの非一意のインデックス、クエリヒット:UPDATEの学生は= 100名= 'トムスコアを設定します。
- 二つの非一意のインデックス、クエリミス:UPDATEの学生は、スコア= 100 WHERE名=「ジョン」SET。
- 无索引:UPDATEの学生は、スコア= 100、スコア= 22を設定します。
- クラスタ化インデックスの範囲クエリ:UPDATE学生は、スコア= 100、ID <= 20を設定します。
- 二つのインデックス範囲クエリ:UPDATEの学生は、スコア= 100 WHERE年齢<= 23を設定します。
- インデックス値を変更します。UPDATEの学生は、名前、ID = 15 =「ジョン」を設定。
2.1クラスタ化インデックス、クエリヒット
文 UPDATE students SET score = 100 WHERE id = 15
次のようにRCとRR分離レベルの場合のようにロックが、これは、IDプラスXロックにクラスタ化インデックスです。
2.2クラスタ化インデックス、クエリミス
クエリがレコードのロックをミスした場合GAP RRロックがあるので、RCおよびRR分離レベルでは同じではありません。ステートメント UPDATE students SET score = 100 WHERE id = 16
RCおよびRR分離レベルでロックする場合、以下のように(RCロック解除)。
2.3 2つのユニークなインデックス、クエリヒット
声明は、 UPDATE students SET score = 100 WHERE no = 'S0003'
ブログ上の2つの一意のインデックスを打つ、我々は、我々は、インデックスの2つのリーフノードは、主キーのインデックスの位置を保持していることを知っているインデックスの構造を導入した二次インデックスにロックされたときに、主キーインデックスは次のようになりますそしてロック。これは、RCおよびRR分離レベルの二種類に差はありません。
なぜ主キーのインデックスのレコードは、それをロックしていますか?:があるかもしれないので、他のトランザクションは、次のような学生の主キーテーブルに従って更新されるUPDATE students SET score = 100 WHERE id = 20
主キーのインデックスがロックされていない場合は、明らかに同時の問題があるでしょう想像してみてください。
2.4 2つのユニークなインデックス、クエリミス
クエリが同じ状況を記録した、と2.2ていない場合は、RR分離レベルは、RCロックなし、GAPロックが増加します。文の UPDATE students SET score = 100 WHERE no = 'S0008'
ロック次のように:
この場合、2つのだけのロックインデックスでは、ロックにインデックスをクラスタ化されていません。
2.5二つの非一意のインデックス、クエリヒット
クエリヒットがRR分離レベルでは、第二の非一意のインデックスですが、また、GAPロックを追加した場合。文の UPDATE students SET score = 100 WHERE name = 'Tom'
ロック次のように:
ユニークインデックスがGAPがそれをロックし追加していないながら、なぜ非一意のインデックスは、GAPのロックを増やすのだろうか?その理由は単純で、ファントムを解決するためのGAPロックの効果は、インデックスの同じ値を持つレコードを挿入、他のトランザクションを阻止、読んで、唯一のインデックスと主キー制約は、インデックス値は確かに1つのレコードだけなので、GAPロックを追加する必要はありませんであることを確認する必要があります。
这里还有一点要注意一下,数一数右图中的锁你可能会觉得一共加了 7 把锁,实际情况不是,要注意的是 (Tom, 37) 上的记录锁和它前面的 GAP 锁合起来是一个 Next-key 锁,这个锁加在 (Tom, 37) 这个索引上,另外 (Tom, 49) 上也有一把 Next-key 锁。那么最右边的 GAP 锁加在哪呢?右边已经没有任何记录了啊。其实,在 InnoDb 存储引擎里,每个数据页中都会有两个虚拟的行记录,用来限定记录的边界,分别是:Infimum Record
和 Supremum Record
,Infimum 是比该页中任何记录都要小的值,而 Supremum 比该页中最大的记录值还要大,这两条记录在创建页的时候就有了,并且不会删除。上面右边的 GAP 锁就是加在 Supremum Record 上。所以说,上面右图中共有 2 把 Next-key 锁,1 把 GAP 锁,2 把记录锁,一共 5 把锁。
2.6 二级非唯一索引,查询未命中
如果查询未命中纪录,和 2.2、2.4 情况一样,RR 隔离级别会加 GAP 锁,RC 无锁。语句 UPDATE students SET score = 100 WHERE name = 'John'
加锁情况如下:
2.7 无索引
如果 WHERE 条件不能走索引,MySQL 会如何加锁呢?有的人说会在表上加 X 锁,也有人说会根据 WHERE 条件将筛选出来的记录在聚簇索引上加上 X 锁,那么究竟如何,我们看下图:
在没有索引的时候,只能走聚簇索引,对表中的记录进行全表扫描。在 RC 隔离级别下会给所有记录加行锁,在 RR 隔离级别下,不仅会给所有记录加行锁,所有聚簇索引和聚簇索引之间还会加上 GAP 锁。
语句 UPDATE students SET score = 100 WHERE score = 22
满足条件的虽然只有 1 条记录,但是聚簇索引上所有的记录,都被加上了 X 锁。那么,为什么不是只在满足条件的记录上加锁呢?这是由于 MySQL 的实现决定的。如果一个条件无法通过索引快速过滤,那么存储引擎层面就会将所有记录加锁后返回,然后由 MySQL Server 层进行过滤,因此也就把所有的记录都锁上了。
不过在实际的实现中,MySQL 有一些改进,如果是 RC 隔离级别,在 MySQL Server 过滤条件发现不满足后,会调用 unlock_row 方法,把不满足条件的记录锁释放掉(违背了 2PL 的约束)。这样做可以保证最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。如果是 RR 隔离级别,一般情况下 MySQL 是不能这样优化的,除非设置了 innodb_locks_unsafe_for_binlog
参数,这时也会提前释放锁,并且不加 GAP 锁,这就是所谓的 semi-consistent read,关于 semi-consistent read 可以参考 这里。
2.8 聚簇索引,范围查询
上面所介绍的各种情况其实都是非常常见的 SQL,它们有一个特点:全部都只有一个 WHERE 条件,并且都是等值查询。那么问题来了,如果不是等值查询而是范围查询,加锁情况会怎么样呢?有人可能会觉得这很简单,根据上面的加锁经验,我们只要给查询范围内的所有记录加上锁即可,如果隔离级别是 RR,所有记录之间再加上间隙锁。事实究竟如何,我们看下面的图:
SQL 语句为 UPDATE students SET score = 100 WHERE id <= 20
,按理说我们只需要将 id = 20、18、15 三条记录锁住即可,但是看右边的图,在 RR 隔离级别下,我们还把 id = 30 这条记录以及 (20, 30] 之间的间隙也锁起来了,很显然这是一个 Next-key 锁。如果 WHERE 条件是 id < 20,则会把 id = 20 这条记录锁住。为什么会这样我也不清楚,网上搜了很久,有人说是为了防止幻读,但 id 是唯一主键,(20, 30] 之间是不可能再插入一条 id = 20 的,所以具体的原因还需要再分析下,如果你知道,还请不吝赐教。
所以对于范围查询,如果 WHERE 条件是 id <= N,那么 N 后一条记录也会被加上 Next-key 锁;如果条件是 id < N,那么 N 这条记录会被加上 Next-key 锁。另外,如果 WHERE 条件是 id >= N,只会给 N 加上记录锁,以及给比 N 大的记录加锁,不会给 N 前一条记录加锁;如果条件是 id > N,也不会锁前一条记录,连 N 这条记录都不会锁。
====================== 11月26号补充 =========================
我在做实验的时候发现,在 RR 隔离级别,条件是 id >= 20,有时会对 id < 20 的记录加锁,有时候又不加,感觉找不到任何规律,请以实际情况为准。我对范围查询的加锁原理还不是很明白,后面有时间再仔细研究下,也欢迎有兴趣的同学一起讨论下。
下面是我做的一个简单的实验,表很简单,只有一列主键 id:
1 2 3 4 5 6 7 8 9 |
|
表里一共三条数据:
1 2 3 4 5 6 7 8 9 |
|
执行 delete from t1 where id > 2
时加锁情况是:(2, 4], (4, 6], (6, +∞)
执行 select * from t1 where id > 2 for update
时加锁情况是:(-∞, 2], (2, 4], (4, 6], (6, +∞)
可见 select for update 和 delete 的加锁还是有所区别的,至于 select for update 为什么加 (-∞, 2] 这个锁,我还是百思不得其解。后来无意中给表 t1 加了一个字段 a int(11) NOT NULL,竟然发现 select * from t1 where id > 2 for update
就不会给 (-∞, 2] 加锁了,真的非常奇怪。
====================== 12月3号补充 =========================
经过几天的搜索,终于找到了一个像样的解释(但不好去证实):当数据表中数据非常少时,譬如上面那个的例子,select ... [lock in share mode | for update] 语句会走全表扫描,这样表中所有记录都会被锁住,这就是 (-∞, 2] 被锁的原因。而 delete 语句并不会走全表扫描。
2.9 二级索引,范围查询
然后我们把范围查询应用到二级非唯一索引上来,SQL 语句为:UPDATE students SET score = 100 WHERE age <= 23
,加锁情况如下图所示:
可以看出和聚簇索引的范围查询一样,除了 WHERE 条件范围内的记录加锁之外,后面一条记录也会加上 Next-key 锁,这里有意思的一点是,尽管满足 age = 24 的记录有两条,但只有第一条被加锁,第二条没有加锁,并且第一条和第二条之间也没有加锁。
2.10 修改索引值
这种情况比较容易理解,WHERE 部分的索引加锁原则和上面介绍的一样,多的是 SET 部分的加锁。譬如 UPDATE students SET name = 'John' WHERE id = 15
不仅在 id = 15 记录上加锁之外,还会在 name = 'Bob'(原值)和 name = 'John'(新值) 上加锁。示意图如下(<span style='color:red;'>此处理解有误,参见下面的评论区</span>):
RC 和 RR 没有区别。
三、复杂条件加锁分析
前面的例子都是非常简单的 SQL,只包含一个 WHERE 条件,并且是等值查询,当 SQL 语句中包含多个条件时,对索引的分析就相当重要了。因为我们知道行锁最终都是加在索引上的,如果我们连执行 SQL 语句时会使用哪个索引都不知道,又怎么去分析这个 SQL 所加的锁呢?
MySQL 的索引是一个很复杂的话题,甚至可以写一本书出来了。这里就只是学习一下在对复杂 SQL 加锁分析之前如何先对索引进行分析。譬如下面这样的 SQL:
1 |
|
其中 name 和 age 两个字段都是索引,那么该如何加锁?这其实取决于 MySQL 用哪个索引。可以用 EXPLAIN 命令分析 MySQL 是如何执行这条 SQL 的,通过这个命令可以知道 MySQL 会使用哪些索引以及怎么用索引来执行 SQL 的,只有执行会用到的索引才有可能被加锁,没有使用的索引是不加锁的,这里有一篇 EXPLAIN 的博客可以参考。也可以使用 MySQL 的 optimizer_trace 功能 来对 SQL 进行分析,它支持将执行的 SQL 的查询计划树记录下来,这个稍微有点难度,有兴趣的同学可以研究下。那么 MySQL 是如何选择合适的索引呢?其实 MySQL 会给每一个索引一个指标,叫做索引的选择性,这个值越高表示使用这个索引能最大程度的过滤更多的记录,关于这个,又是另一个话题了。
当然,从两个索引中选择一个索引来用,这种情况的加锁分析和我们上一节讨论的情形并没有本质的区别,只需要将那个没有用索引的 WHERE 条件当成普通的过滤条件就好了。这里我们会把用到的索引称为 Index Key,而另一个条件称为 Table Filter。譬如这里如果用到的索引为 age,那么 age 就是 Index Key,而 name = 'Tom' 就是 Table Filter。Index Key 又分为 First Key 和 Last Key,如果 Index Key 是范围查询的话,如下面的例子:
1 |
|
其中 First Key 为 age > 22,Last Key 为 age < 25。
所以我们在加锁分析时,只需要确定 Index Key 即可,锁是加在 First Key 和 Last Key 之间的记录上的,如果隔离级别为 RR,同样会有间隙锁。要注意的是,当索引为复合索引时,Index Key 可能会有多个,何登成的这篇博客《SQL中的where条件,在数据库中提取与应用浅析》详细介绍了如何从一个复杂的 WHERE 条件中提取出 Index Key,推荐一读。这里 也有一篇博客介绍了 MySQL 是如何利用索引的。
当索引为复合索引时,不仅可能有多个 Index Key,而且还可能有 Index Filter。所谓 Index Filter,就是复合索引中除 Index Key 之外的其他可用于过滤的条件。如果 MySQL 是 5.6 之前的版本,Index Filter 和 Table Filter 没有区别,统统将 Index First Key 与 Index Last Key 范围内的索引记录,回表读取完整记录,然后返回给 MySQL Server 层进行过滤。而在 MySQL 5.6 之后,Index Filter 与 Table Filter 分离,Index Filter 下降到 InnoDB 的索引层面进行过滤,减少了回表与返回 MySQL Server 层的记录交互开销,提高了SQL的执行效率,这就是传说中的 ICP(Index Condition Pushdown),使用 Index Filter 过滤不满足条件的记录,无需加锁。
这里引用何登成前辈博客中的一个例子(图片来源):
可以看到 pubtime > 1 and pubtime < 20 为 Index First Key 和 Index Last Key,MySQL 会在这个范围内加上记录锁和间隙锁;userid = 'hdc' 为 Index Filter,这个过滤条件可以在索引层面就可以过滤掉一条记录,因此如果数据库支持 ICP 的话,(4, yyy, 3) 这条记录就不会加锁;comment is not NULL 为 Table Filter,虽然这个条件也可以过滤一条记录,但是它不能在索引层面过滤,而是在根据索引读取了整条记录之后才过滤的,因此加锁并不能省略。
四、DELETE 语句加锁分析
一般来说,DELETE 的加锁和 SELECT FOR UPDATE 或 UPDATE 并没有太大的差异,DELETE 语句一样会有下面这些情况:
- 聚簇索引,查询命中:DELETE FROM students WHERE id = 15;
- 聚簇索引,查询未命中:DELETE FROM students WHERE id = 16;
- 二级唯一索引,查询命中:DELETE FROM students WHERE no = 'S0003';
- 二级唯一索引,查询未命中:DELETE FROM students WHERE no = 'S0008';
- 二级非唯一索引,查询命中:DELETE FROM students WHERE name = 'Tom';
- 二级非唯一索引,查询未命中:DELETE FROM students WHERE name = 'John';
- 无索引:DELETE FROM students WHERE score = 22;
- 聚簇索引,范围查询:DELETE FROM students WHERE id <= 20;
- 二级索引,范围查询:DELETE FROM students WHERE age <= 23;
针对这些情况的加锁分析和上文一致,这里不再赘述。
那么 DELETE 语句和 UPDATE 语句的加锁到底会有什么不同呢?我们知道,在 MySQL 数据库中,执行 DELETE 语句其实并没有直接删除记录,而是在记录上打上一个删除标记,然后通过后台的一个叫做 purge 的线程来清理。从这一点来看,DELETE 和 UPDATE 确实是非常相像。事实上,DELETE 和 UPDATE 的加锁也几乎是一样的,这里要单独加一节来说明 DELETE 语句的加锁分析,其实并不是因为 DELETE 语句的加锁和其他语句有所不同,而是因为 DELETE 语句导致多了一种特殊类型的记录:标记为删除的记录,对于这种类型记录,它的加锁和其他记录的加锁机制不一样。所以这一节的标题叫做 标记为删除的记录的加锁分析 可能更合适。
那么问题又来了:什么情况下会对已标记为删除的记录加锁呢?我总结下来会有两种情况:阻塞后加锁 和 快照读后加锁(自己取得名字),下面分别介绍。
阻塞后加锁 如下图所示,事务 A 删除 id = 18 这条记录,同时事务 B 也删除 id = 18 这条记录,很显然,id 为主键,DELETE 语句需要获取 X 记录锁,事务 B 阻塞。事务 A 提交之后,id = 18 这条记录被标记为删除,此时事务 B 就需要对已删除记录进行加锁。
快照读后加锁 如下图所示,事务 A 删除 id = 18 这条记录,并提交。事务 B 在事务 A 提交之前有一次 id = 18 的快照读,所以在后面删除 id = 18 这条记录的时候就需要对已删除记录加锁了。如果没有事务开头的这个快照读,DELETE 语句就只是简单的删除一条不存在的记录。
注意,上面的事务 B 不限于 DELETE 语句,换成 UPDATE 或 SELECT FOR UPDATE 同样适用。网上对这种删除记录的加锁分析并不多,我通过自己做的实验,得到了下面这些结论,如有不正确的地方,欢迎斧正。(实验环境,MySQL 版本:5.7,隔离级别:RR)
-
删除记录为聚簇索引
- 阻塞后加锁:在删除记录上加 X 记录锁(rec but not gap),并在删除的后一条记录上加间隙锁(gap before rec)
- 快照读后加锁:在删除记录上加 X 记录锁(rec but not gap)
-
删除记录为二级索引(唯一索引和非唯一索引都适用)
- 阻塞后加锁:在删除记录上加 Next-key 锁,并在删除的后一条记录上加间隙锁
- 快照读后加锁:在删除记录上加 Next-key 锁,并在删除的后一条记录上加间隙锁
要注意的是,这里的隔离级别为 RR,如果在 RC 隔离级别下,加锁过程应该会不一样,感兴趣的同学可以自行实验。关于 DELETE 语句的加锁,何登成前辈在他的博客:一个最不可思议的MySQL死锁分析 里面有详细的分析,并介绍了页面锁的相关概念,还原了仅仅只有一条 DELETE 语句也会造成死锁的整个过程,讲的很精彩。
五、INSERT 语句加锁分析
上面所提到的加锁分析,都是针对 SELECT FOR UPDATE、UPDATE、DELETE 等进行的,那么针对 INSERT 加锁过程又是怎样的呢?我们下面继续探索。
还是用 students 表来实验,譬如我们执行下面的 SQL:
1 |
|
然后我们用 show engine innodb status\G
查询事务的锁情况:
1 2 3 4 |
|
我们发现除了一个 IX 的 TABLE LOCK 之外,就没有其他的锁了,难道 INSERT 不加锁?一般来说,加锁都是对表中已有的记录进行加锁,而 INSERT 语句是插入一条新的纪录,这条记录表中本来就没有,那是不是就不需要加锁了?显然不是,至少有两个原因可以说明 INSERT 加了锁:
- 为了防止幻读,如果记录之间加有 GAP 锁,此时不能 INSERT;
- 如果 INSERT 的记录和已有记录造成唯一键冲突,此时不能 INSERT;
要解决这两个问题,都是靠锁来解决的(第一个加插入意向锁,第二个加 S 锁进行当前读),只是在 INSERT 的时候如果没有出现这两种情况,那么锁就是隐式的,只是我们看不到而已。这里我们不得不提一个概念叫 隐式锁(Implicit Lock),它对我们分析 INSERT 语句的加锁过程至关重要。
关于隐式锁,这篇文章《MySQL数据库InnoDB存储引擎中的锁机制》对其做了详细的说明,讲的非常清楚,推荐一读。可以参考上一篇介绍的悲观锁和乐观锁。
锁是一种悲观的顺序化机制,它假设很可能发生冲突,因此在操作数据时,就加锁,如果冲突的可能性很小,多数的锁都是不必要的。Innodb 实现了一个延迟加锁的机制来减少加锁的数量,这被称为隐式锁。
隐式锁中有个重要的元素:事务ID(trx_id)。隐式锁的逻辑过程如下:
A. InnoDB 的每条记录中都有一个隐含的 trx_id 字段,这个字段存在于簇索引的 B+Tree 中;
B. 在操作一条记录前,首先根据记录中的 trx_id 检查该事务是否是活动的事务(未提交或回滚),如果是活动的事务,首先将隐式锁转换为显式锁(就是为该事务添加一个锁);
C. 检查是否有锁冲突,如果有冲突,创建锁,并设置为 waiting 状态;如果没有冲突不加锁,跳到 E;
D. 等待加锁成功,被唤醒,或者超时;
E. 写数据,并将自己的 trx_id 写入 trx_id 字段。隐式锁的特点是只有在可能发生冲突时才加锁,减少了锁的数量。另外,隐式锁是针对被修改的 B+Tree 记录,因此都是 Record 类型的锁,不可能是 Gap 或 Next-Key 类型。
- INSERT 操作只加隐式锁,不需要显示加锁;
- UPDATE、DELETE 在查询时,直接对查询用的 Index 和主键使用显示锁,其他索引上使用隐式锁。
理论上说,可以对主键使用隐式锁的。提前使用显示锁应该是为了减少死锁的可能性。INSERT,UPDATE,DELETE 对 B+Tree 们的操作都是从主键的 B+Tree 开始,因此对主键加锁可以有效的阻止死锁。
INSERT 加锁流程如下(参考):
-
首先对插入的间隙加插入意向锁(Insert Intension Locks)
- 如果该间隙已被加上了 GAP 锁或 Next-Key 锁,则加锁失败进入等待;
- 如果没有,则加锁成功,表示可以插入;
-
然后判断插入记录是否有唯一键,如果有,则进行唯一性约束检查
- 如果不存在相同键值,则完成插入
-
如果存在相同键值,则判断该键值是否加锁
-
如果没有锁, 判断该记录是否被标记为删除
- 如果标记为删除,说明事务已经提交,还没来得及 purge,这时加 S 锁等待;
- 削除用にマークされていない場合は、1062重複キーエラーが報告されています。
- ロックは、レコードが処理されていることを示す、存在する場合(追加、削除、または更新)、およびトランザクションが提出されていない、プラスSロック待ち。
-
- レコードとレコードロック記録プラスXを挿入します。
ここでの表現は、私が、興味のある学生は、ソースコード解析INSERT文InnoDBは、特定のロック手順を読みに行くことができ、実際には正確ではない 「MySQLのソースコードを見てのINSERTロック処理を読んで」 このブログの詳細な説明。
参照
- 彼Dengcheng技術のブログ - MySQLのロック処理および分析
- 彼Dengcheng技術ブログ - MySQLのInnoDBの+半consitent読み取り原理と分析
- 彼Dengcheng技術ブログ - データベース内の条件のSQL、抽出および分析アプリケーション
- 彼Dengcheng技術のブログ - 最も信じられないほどの1のMySQLのデッドロック分析
- Yilunファンのブログ - MySQLのロック分析
- 10分には、あなたはMySQLがインデックスをどのように使用するかを理解させます
- MySQLのInnoDBの分析をロックする様々なSQL文
- MySQLは同時実行制御と分析をロックします
- クラスカルのブログ - InnoDBのロック
- MySQLデータベースのロック機構にInnoDBストレージエンジン
- MySQLはステートメントがロックされた分析を削除DELETE
- 詳細なロックアルゴリズムのMySQLのロック(G)
- [MySQLの5.6]知人5.6オプティマイザのトレース
- (4)の挿入/ロックプロセス例示的なデッドロックの分析を削除INNODBロックシステム[MySQLを学習]