基本的にはMySQLのベースロックステップアウト、上の基本的なロックを導入することにより、大まかなアイデアを持っています。ロックギャップ:次はロックの最終号を入力します。これらは、はるかに複雑で、ギャップロックとロー・ロック、テーブルロックは、読書の魔法を解決するための主要な手段です。これにより、我々は同時大規模の大きな下落につながった、最も厳しいレベルをシリアル化するトランザクション分離レベルを調整する必要はありません。この記事を読む前に、私が思うに、すべての上に、私たちは本当に徹底していない私の前の二つの記事、またはいくつかのアイデアを読みたいです:
テストデータの基本的なテーブル
全体のギャップ深さをロックするために、我々はいくつかの基本的なデータを構築したい、この章の基本的なデータ我々は、データテーブルとインデックスの確立後、構築するために使用します。
create table `t` (
`id` int(11) not null,
`c` int(11) DEFAULT null,
`d` int(11) DEFAULT null,
primary key (`id`),
key `c` (`c`)
) ENGINE=InnoDB;
その後、我々はいくつかの基本的なデータを挿入します。
insert into t values
(0,0,0),
(5,5,5),
(10,10,10),
(15,15,15),
(20,20,20),
(25,25,25);
加えて、我々はこれを説明すると、デフォルトのデータベースの分離レベルを使用している:反復可能読み取り
第二に、いわゆるファントムを読みます
さて、問題は重要になります!古典シーンファントムが発生詳細読み、のは手の込んだしてみましょう。実際には非常にシンプルな、次の特定の再生のSQL文を見て:
セッションA | セッションB | セッションC | |
---|---|---|---|
T1 | ベギン; | ||
T更新用D = 5 SELECT * FROM。 | |||
T2 | 更新T集合D = 5ここで、ID = 0。 | ||
T3 | T更新用D = 5 SELECT * FROM。 | ||
T4 | t値(1,1,5)に挿入 | ||
T5 | T更新用D = 5 SELECT * FROM。 | ||
T6 | コミット; |
この一連の動作のために、私たちは一つ一つを分析する必要があります。
- 時刻t1においてセッションA、読み出し結果が表示されている:(5,5,5)は、ID 5、プラスライン書き込みロックロックとは、インデックスので、フルテーブルスキャン、行dはありません
- 次いで、セッションB時間t2ので、IDは結果が時間t3を読み取る見ることができる、データ0に変更、セッションAは、(0,0,5)、(5,5,5)
- セッションC再び時刻t4において、データが挿入されたストリップが存在しないため、時刻t6を読み取る可視結果は、セッションAは、(0,0,0)、(1,1,5)、(5,5,5)
- 私たちは、スナップショット機構MVCCを作成する影響により正常な状況の読み取りの一貫性、下表示され、更新のために読んで追加しない場合、セッションAは常にのみ(5,5,5)が表示されますこれは、データの一部であります
- 更新後、チェックアウト見えるが、1つのより多くのデータを読み、読書は、魔法ではないデータを読み出し、挿入、読み取り後にだけ見える、と呼ばれるファントム読み取り。それは我々が行うよりも2つの生データのようだったでしょう、ではなく周りのトランザクションが終了する前に読むために、3つのデータ私はファントム後に読み出す場合は、2及び第3条、余分に1が読み出された、のような、突然現れ、いわゆるファントムを読み込みます。
- 我々は通常更新ステートメントの前にあることを覚えて、しかし、更新クエリの選択使用はほとんどないが、選択クエリの更新のために実施することです!
第三に、ファントムは、どのような影響読みます
程度で、以下のように2つの効果があります。
1、セマンティック競合
select * from t where d = 5 for update;
同様に、我々はこの文は、実際には、上記のすべてのセマンティクスをロックする必要があり、データの5行は、更新および挿入することが許されないDに等しいです。ファントム読み取りを解決するために該当するメカニズムが存在しない場合は、私たちの次のセッションBとセッションCは内部に、その後、問題があるでしょう。
-- sessionB增加点操作
update t set d = 5 where id = 0;
update t set c = 5 where id = 0;
操作された第2の可視SQL IDが0に等しく、Dはすべての行の意味論的競合5上記以前のロックに等しいデータ線5に等しいです。
2、データの整合性の問題
これは、binglogに関連する問題は非常に重要です。以下は、私たちのSQLテーブルの具体的な動作です。
セッションA | セッションB | セッションC | |
---|---|---|---|
T1 | ベギン; | ||
T更新用D = 5 SELECT * FROM。 | |||
更新TセットD = 5、D = 100。 | |||
T2 | 更新T集合D = 5ここで、ID = 0。 | ||
TセットC = 5、ID = 0を更新します。 | |||
T3 | T更新用D = 5 SELECT * FROM。 | ||
T4 | t値(1,1,5)に挿入します。 | ||
TセットC = 5、ID = 1を更新します。 | |||
T5 | T更新用D = 5 SELECT * FROM。 | ||
T6 | コミット; |
、binglogコミット後に待機しているので、それは(後で記事が詳細を説明します)を記録、そう見えるようにbinglogになりますSQL操作、上記一連のでしょう。
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/
あなたは私たちが先に言ったように、この行の5と同じだけのID、セッションBの動作が行われるので、我々は最終的に我々はセッションA更新操作内であれば、最後に実行されていることがわかります、行ロックを追加し、見ることができますこのbinglogの同期を取り、それは必然的に、(0,5,100)、ライブラリから鉛(1,5,100)と(5,5,100)は、このデータが表示され、メインのライブラリが完全に矛盾してます!(メインライブラリ、データのみ番号5の内部には、Dはわずか100です)。
その後、我々はすべて、その後どうなるロック、第二のデータ行をスイープでしょうか?ブロックされその後、セッションB最初のUPDATE文、binglog次のデータ:
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
このような結果は、この行番号0のデータは、実際にデータの保証は、しかし、あなたは、dは5主なライブラリである、ちょうど1としてのIDを挿入し、見つけるだろうしていることができますが、ライブラリ内からbinglog実行した後に、それは100に変わり、状況は矛盾が登場します!
第四に、入力する「ギャップロック」
しばしば「暗唱」という問題、私たちの日常の理論を読んファントムの場合:第三トランザクション分離レベルは、ファントム読み取り表示され、唯一の分離レベル、最高レベルのシリアライズを増やすことで、この問題ファントム読み取り解決することができます。しかし、同時性が大幅に減少し、これは、同じテーブルを操作するたびに一つだけのスレッド、単純にこの事、あなたが知っている、MySQLの高い同時実行のための需要を満たすことはできませんが、基礎となるデータベースのトップのインターネット企業はどのように、ラッシュエネルギー効率はそれほど悪いですか?ここでは、ロックの概念の導入にMySQLギャップ。私たちは、ギャップロックがロックする方法を見てみましょう。
1、ギャップが生成されるロック
まず、我々は次のクエリ文を使用している場合:
select * from t where d = 5 for update;
したがって、Dが索引付けされていないので、それはデフォルトのIDの主キーインデックスをとる、フル・テーブル・クエリを取る、IDに従ってプライマリ・キー値は、以下の範囲を生成します。
ギャップロックを増加させる方法2、
上記のため、次に、上記のようなSELECT文は、Dは、インデックスIDによって、そうフルテーブルスキャン面をインデックス付けされず、また、それが更新するためのものであるため、表は、6本のデータ線は、ロックに結合されるであろう、そして6つのセクションが追加されますクリアランスロックを。ラインロック+ロックギャップ私たちが愛したものです:ネクストキーロックアップ!だから、全体では7ネクストキーロックは、次のとおりです。
これは、+∞が存在しない各インデックスに値を割り当てるように構成することができるされています
3、ギャップロック機能
前の記事では、我々は相互に排他的な行ロックの間で形成するように見えたし話を聞きました:
ロックを読みます | ロックを書きます | |
---|---|---|
ロックを読みます | コンパチブル | コンフリクト |
ロックを書きます | コンフリクト | コンフリクト |
しかし、ギャップはロックではありません。クリアランスロックの競合は、データ内の隙間に挿入されています!リストアの並行性を維持するためにも良いです。次のアクションを参照してください。
セッションA | セッションB |
---|---|
ベギン; | |
ここで、共有モデルにおけるc = 7ロックT SELECT * FROM。 | |
ベギン; | |
ここで、c =アップデートのための7トンSELECT * FROM。 |
二つのトランザクションが、同じデータに実際にある、目に見えてクエリを読みますが、目詰まりしていません!いかなる値c 7が存在しないので、結果のみを添加データベース(5,10)において、2つのギャップが見られなくなり、このギャップをロックし、相互排他ロックとの競合を読み取ることです!
もしそうであれば、前のセクションのSQLの動作のためにロックの特性ギャップ特性、および行ロックを追加します。
セッションA | セッションB | セッションC | |
---|---|---|---|
T1 | ベギン; | ||
T更新用D = 5 SELECT * FROM。 | |||
更新TセットD = 5、D = 100。 | |||
T2 | 更新T集合D = 5ここで、ID = 0。(阻塞) | ||
TセットC = 5、ID = 0を更新します。 | |||
T3 | T更新用D = 5 SELECT * FROM。 | ||
T4 | Tの値に(1,1,5)を挿入し; (ブロッキング) | ||
TセットC = 5、ID = 1を更新します。 | |||
T5 | T更新用D = 5 SELECT * FROM。 | ||
T6 | コミット; |
最终生成的binglog就会是:
update t set d=100 where d=5;/* d 改成 100*/
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
这样,就解决了数据一致性的问题了,主从库里面都能保持一致。
4、间隙锁带来的问题
虽然,间隙锁能比较好的解决上诉我们探讨的问题,但是同时也会带来些麻烦,要我们特别的注意。例如下面的操作,是一段业务上面的伪代码:
tx.beginTransaction();
var t = select * from t where id = 9 for update;
if(t){
update t set d = 45 where id = 9;
}else{
insert into t values(9,5,45);
}
tx.commit();
(假设id等于9这一行不存在)这段业务逻辑代码,普通情况下,我也经常看到,问题不太会出现,一旦并发量上去了,就会出问题,会造成死锁,下面我们看看造成死锁的sql执行序列:
sessionA | sessionB | |
---|---|---|
t1 | begin; | |
select * from t where id = 9 for update; | ||
begin; | ||
t2 | select * from t where id = 9 for update; | |
insert into t values(9,5,45);(阻塞,等待sessionA的(5,10)的间隙锁释放) | ||
t3 | insert into t values(9,5,45); (阻塞,等待sessionB的(5,10)的间隙锁释放,死锁!) |
当然,InnoDB的自动死锁检测,会发现这一点,主动将sessionA回滚,报错!
五、晋级"间隙锁"
有关于间隙锁,是最后一层级的细节所在,所以在判断是否加、怎么加、是否会阻塞方面,有非常多的考量。接下来我们来分别来说一下4个细节,分别对应4个例子,来讲讲,首先我们列出五条规则:
- 加锁的基本单位是next-key lock,就是针对扫描过的数据进行加间隙锁
- 索引上进行等值查询时,给唯一索引加锁的时候,next-key lock退化为行锁
- 索引上进行等值查询时,向右遍历,最后一个数值不满足等值的条件的时候,next-key lock退化为间隙锁,就是前后都是开区间
- 唯一索引的范围查询,会访问到第一个不满足的条件为止
1、第一条规则
加锁的基本单位是next-key lock,就是针对扫描过的数据进行加间隙锁
先来看看几个sql语句:
select * from t where id = 5 for update;
select * from t where id = 10 lock in share model;
两个分别对5和10这两行加了写锁与读锁,但是最开始,再索引树上面,首先加载id为5和10的这两行的时候,加锁步骤如下:
- 加(0,5)和(5,10)这两个间隙锁
- 加5的这一行的行锁(写锁),加10这一行的行锁(读锁)
- 所以目前为止,基础加锁的单位为next-key lock
2、第二条规则
索引上进行等值查询时,给唯一索引加锁的时候,next-key lock退化为行锁
还是第一条规则的两天语句,发现,id是主键索引(唯一索引),所以去掉了(0,5)(5,10)的这两个间隙锁,所以整个next-key lock变成了单纯的行锁
3、第三条规则
索引上进行等值查询时,向右遍历,最后一个数值不满足等值的条件的时候,next-key lock退化为间隙锁,就是前后都是开区间
先来看看下面的操作过程:
sessionA | sessionB | sessionC |
---|---|---|
begin; | ||
update t set d = d+1 where id = 7; | ||
insert into t values (8,8,8);(阻塞!) | ||
update t set d = d+1 where id = 10;(成功) |
我们来分析:
- update之前会进行select for update操作,所以就是对id为7的这一行进行可见读
- 由于7这行记录不存在,但是7落在了(5,10)这个区间,而根据第一条原则,加锁基本单位是next-key lock,所以加锁会加上(5,10)的间隙锁,和10这一行的行锁(写锁),就是(5,10]
- 由于最后一条记录10和等值查询中的7并不相等,所以退化成了间隙锁,10这个的行锁解除。
所以根据这个规则,(5,10)这个区间是被锁住额,所以insert会被阻塞,另外10这一行的行锁解除,所以sessionC中的update会成功。
4、第四条规则
唯一索引的范围查询,会访问到第一个不满足的条件为止
看看下面的操作序列:
sessionA | sessionB | sessionC |
---|---|---|
begin; | ||
select * from t where id > 10 and id <=15 for update; | ||
update t set d = d+1 where id = 20;(阻塞) | ||
insert into t values(16,16,16);(阻塞) |
分析:
- 由于10不是等值,15是等值,所以10这一条不会加next-key lock,15会,所以首先加上了(10,15]
- 虽然是唯一索引,但是是区间查询,并不会停止加锁的脚步,会继续向右
- 找到20这条记录,加上了next-key lock的(15,20]
- 由于不是等值查询,是范围查询,所以应用不了规则三,所以最终形成的锁是:(10,15],(15,20]
这么一看,20这一行被行锁锁住,而且15,20的区间还有间隙锁,所以sessionB和sessionC的操作才会阻塞。
5、其他方面的细节
每次加锁,其实都是锁索引树。众所周知,InnoDB的非主键索引的最终叶子节点,都只存储id主键值,然后还要遍历id主键索引,才能搜索出整条的数据,我们通常将这个过程称之为:回表。当然,如果select的是一个字段,这个字段刚好是id,那么Mysql就不用进行回表查询,因为直接在索引树上就能读取到值,MySQL会进行这种优化,通常我们称之为:索引下推。根据这个特性,我们来看看下面的操作序列:
sessionA | sessionB | sessionC |
---|---|---|
begin; | ||
select id from t where c = 5 lock in share model; | ||
update t set d = d+1 where id = 5;(成功) | ||
insert into t values(3,3,3);(阻塞) |
- 只在c这个非唯一索引上,加了行读锁,基础的加锁单位是(0,5],由于是非唯一索引的查询,并不能退化为行锁
- 由于非唯一索引,要继续往下,加上了(5,10]这一个的next-key lock,由于最右边的最后一个值,和等值查询并不相等,所以退化成间隙锁(5,10),所以sessionC会被阻塞
- 由于sessionA中的可见读是读锁,并且只查询id的值,所以启动了索引下推优化,只会加c这个索引上面的行锁。如果换成for update,那就会顺便将主键索引上面也加上锁。所以这里要分清两种行锁的粒度。
- 所以,最后,sessionB能成功的愿意是:主键索引上并没有加锁
六、结束
ロックは、能力の私のサークルでは、そんなに言うことができ、または特定の練習のために使用されます。次に、私は2つの重要なログファイルを導入するために書くつもり:binglogとredoログ
ます。https://my.oschina.net/UBW/blog/3059914で再現