この記事では、MySQL のトランザクションを理解し、トランザクション分離の実装原理について説明します。お役に立てば幸いです。
データベース トランザクションと言えば、トランザクションの ACID 特性、分離レベル、解決された問題など、多くのトランザクション関連の知識が誰の頭にも容易に浮かぶはずです。 (ダーティ リード、ノンリピータブル リード、ファントム リード) などがありますが、トランザクションのこれらの機能がどのように実装されているか、およびなぜ 4 つの分離レベルがあるのかを実際に知っている人はほとんどいないかもしれません。
今日はまず、MySQL におけるトランザクション分離の実装原理について説明し、引き続き他の機能の実装原理を分析する記事を公開していきます。
もちろん、MySQL は広範囲かつ奥が深いため、記事の省略は避けられません。批判や修正は歓迎です。
説明
MySQL のトランザクション実装ロジックはエンジン層にあり、すべてのエンジンがトランザクションをサポートしているわけではありません。次の手順は InnoDB エンジンに基づいています。
分離とは、さまざまなトランザクションが次々に送信されて実行された後、最終的な影響は連続的であるという事実を指します。つまり、トランザクションの場合、それは実行中です。このプロセスでは、認識されるデータの変更は自分自身の操作によってのみ発生する必要があり、他のトランザクションによってデータの変更が発生してはなりません。
分離により、同時トランザクションの問題が解決されます。
分離を実装する最も簡単な方法は、各トランザクションをシリアルに実行することです。前のトランザクションが完了していない場合、後続のトランザクションは待機します。ただし、この実装方法は明らかに同時実行効率が低く、実際の環境での使用には適していません。
上記の問題を解決し、さまざまなレベルの同時実行制御を実現するために、SQL 標準の作成者は、非コミット読み取り (コミットされていない読み取り)、コミット読み取り (コミットされた読み取り)、反復可能読み取り (反復可能) 読み取りというさまざまな分離レベルを提案しました。 )、シリアル化された読み取り (シリアル化可能)。最も高度な分離レベルはシリアル化読み取りであり、他の分離レベルではトランザクションが同時に実行されるため、ある程度の問題は許容されます。次のマトリックス表を参照してください。
#分離レベル (: 表示を許可、-: 表示を許可しない) | ダーティ リード | ##繰り返し不可能なリード##ファンタジー リード | ## ## |
---|
実装方法 | |
---|---|
トランザクションは現在読み取られているデータをロックしません; | トランザクションは現在、特定のデータを更新しています大量のデータ (つまり、更新が発生した瞬間) を処理するには、最初に 行レベルの共有ロック をデータに追加する必要があります。この共有ロックは、トランザクションが終了するまで解放されません。 |
トランザクションは、現在読み取られているデータに | 行レベルの共有ロックを追加します (読み取り時のみロックされます)、行が読み取られると、行レベルの共有ロックはすぐに解放されます。トランザクションが特定のデータを更新した瞬間 (つまり、更新が発生した瞬間)、最初に row を追加する必要があります。 -レベルの排他ロック 、トランザクションが終了するまで解放されません。 |
トランザクションが特定のデータを読み取る瞬間 (読み取りを開始する瞬間)、まずそのデータに | 行を追加する必要があります。 レベル共有ロックはトランザクションが終了するまで解放されません; トランザクションが特定のデータを更新した瞬間(つまり、更新が発生した瞬間)、最初に 行レベルの排他ロックを追加する必要があります。 lock 、トランザクションが終了するまで解放されません。 |
トランザクションがデータを読み取るときは、トランザクションが終了するまで、まず | テーブル レベルの共有ロックを追加する必要があります。 トランザクションがデータを更新するときは、まず テーブル レベルの排他ロック を追加する必要があります。このロックはトランザクションが終了するまで解放されません。 |
事务隔离级别 | 实现方式 |
---|---|
未提交读(RU) | 事务对当前被读取的数据不加锁,都是当前读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。 |
提交读(RC) | 事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。 |
可重复读(RR) | 事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。 通过间隙锁,在这个级别MySQL就解决了幻读的问题 通过快照,在这个级别MySQL就解决了不可重复读的问题 |
序列化读(S) | 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读; 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。 |
可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读问题,大大提升了数据库的并发能力。
不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是RR级别,当然MySQL使用MVCC在RC级别就解决了这个问题)
幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是S级别,当然MySQL使用间隙锁在RR级别就解决了这个问题)
网上很多文章提到幻读和提交读的时候,有的说幻读包括了delete的情况,有的说delete应该属于提交读的问题,那到底真相如何呢?我们实际来看下MySQL的官方文档(如下)
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a
SELECT
) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
可以看到,幻读针对的是结果集前后发生变化,所以看起来delete的情况应该归为幻读,但是我们实际分析下上面列出的标准SQL在RR级别的实现原理就知道,标准SQL的RR级别是会对查到的数据行加行共享锁,所以这时候其他事务想删除这些数据行其实是做不到的,所以在RR下,不会出现因delete而出现幻读现象,也就是幻读不包含delete的情况。
网上很多文章会说MVCC或者MVCC+间隙锁解决了幻读问题,实际上MVCC并不能解决幻读问题。如以下的例子:
begin; #假设users表为空,下面查出来的数据为空 select * from users; #没有加锁 #此时另一个事务提交了,且插入了一条id=1的数据 select * from users; #读快照,查出来的数据为空 update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照 select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照 commit;
可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。如下:
begin; #假设users表为空,下面查出来的数据为空 select * from users lock in share mode; #加上共享锁 #此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待 select * from users; #读快照,查出来的数据为空 update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新 select * from users; #读快照,查出来的数据为空 commit; #事务B提交成功并插入数据
注意,RR级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的。
原文地址:https://segmentfault.com/a/1190000025156465
作者: X先生
【相关推荐:mysql视频教程】
以上がMySQL のトランザクション分離レベルの簡単な分析とその実装原則の説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。