MySQL のトランザクション分離レベルの簡単な分析とその実装原則の説明

青灯夜游
リリース: 2022-03-08 10:21:54
転載
2242 人が閲覧しました

この記事では、MySQL のトランザクションを理解し、トランザクション分離の実装原理について説明します。お役に立てば幸いです。

MySQL のトランザクション分離レベルの簡単な分析とその実装原則の説明

データベース トランザクションと言えば、トランザクションの ACID 特性、分離レベル、解決された問題など、多くのトランザクション関連の知識が誰の頭にも容易に浮かぶはずです。 (ダーティ リード、ノンリピータブル リード、ファントム リード) などがありますが、トランザクションのこれらの機能がどのように実装されているか、およびなぜ 4 つの分離レベルがあるのか​​を実際に知っている人はほとんどいないかもしれません。

今日はまず、MySQL におけるトランザクション分離の実装原理について説明し、引き続き他の機能の実装原理を分析する記事を公開していきます。

もちろん、MySQL は広範囲かつ奥が深いため、記事の省略は避けられません。批判や修正は歓迎です。

説明

MySQL のトランザクション実装ロジックはエンジン層にあり、すべてのエンジンがトランザクションをサポートしているわけではありません。次の手順は InnoDB エンジンに基づいています。

定義

分離とは、さまざまなトランザクションが次々に送信されて実行された後、最終的な影響は連続的であるという事実を指します。つまり、トランザクションの場合、それは実行中です。このプロセスでは、認識されるデータの変更は自分自身の操作によってのみ発生する必要があり、他のトランザクションによってデータの変更が発生してはなりません。

分離により、同時トランザクションの問題が解決されます

標準 SQL 分離レベル

分離を実装する最も簡単な方法は、各トランザクションをシリアルに実行することです。前のトランザクションが完了していない場合、後続のトランザクションは待機します。ただし、この実装方法は明らかに同時実行効率が低く、実際の環境での使用には適していません。

上記の問題を解決し、さまざまなレベルの同時実行制御を実現するために、SQL 標準の作成者は、非コミット読み取り (コミットされていない読み取り)、コミット読み取り (コミットされた読み取り)、反復可能読み取り (反復可能) 読み取りというさまざまな分離レベルを提案しました。 )、シリアル化された読み取り (シリアル化可能)。最も高度な分離レベルはシリアル化読み取りであり、他の分離レベルではトランザクションが同時に実行されるため、ある程度の問題は許容されます。次のマトリックス表を参照してください。

##繰り返し不可能なリード ## Serialized Readingを読むために

MySQL の InnoDB エンジンは、繰り返し読み取りレベルのギャップ ロックを通じてファントム読み取り問題を解決し、MVCC を通じて非繰り返し読み取り問題を解決することに注意してください。詳細については、 以下の分析を参照してください。

実装原則

標準 SQL トランザクション分離レベルの実装原則

上で遭遇した問題は、実際には同時トランザクションでの制御の問題です。同時トランザクションを解決する最も一般的な方法は次のとおりです。悲観的な同時実行制御 (つまり、データベースのロック)。標準 SQL トランザクション分離レベルの実装はロックに依存しています。その実装方法を見てみましょう:

#分離レベル (: 表示を許可、-: 表示を許可しない) ダーティ リード ##ファンタジー リード ## ##
##トランザクション分離レベル 実装方法 #非コミット読み取り (RU) コミット読み取り (RC) 行レベルの共有ロックを追加します (読み取り時のみロックされます) リピータブル リード (RR) 行を追加する必要があります。 レベル共有ロックシリアル化読み取り (S) テーブル レベルの共有ロック

ロックのみを使用して分離レベル制御を実装する場合、頻繁なロックとロック解除が必要となり、読み取りと書き込みの競合が発生しやすいことがわかります (たとえば、RC レベルでは、トランザクション A がデータ行 1 を更新し、トランザクション B は、トランザクション A がデータ行の読み取りをコミットする前に、トランザクション A がコミットしてロックを解放するまで待機する必要があります 1)。

ロックを行わずに読み取り/書き込み競合の問題を解決するために、MySQL は MVCC メカニズムを導入しました。詳細については、以前の分析記事「データベース内の楽観的ロック、悲観的ロック、および MVCC を 1 つの記事で理解する」を参照してください。 。

InnoDB トランザクション分離レベルの実装原則

分析を進める前に、まず理解しておく必要がある概念がいくつかあります:

1. 読み取りのロックと一貫性非ロック読み取り

ロック読み取り: トランザクション内で、SELECT ... LOCK IN SHARE MODE や SELECT ... FOR UPDATE など、読み取りをアクティブにロックします。行共有ロックと行排他ロックがそれぞれ追加されます。ロックの分類については、以前の分析記事「知っておくべき MySQL ロックの分類」を参照してください。

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

一貫した非ロック読み取り: InnoDB MVCC を使用して、トランザクションのクエリに対して特定の時点でのデータベースのスナップショットを提供します。クエリでは、その時点より前にコミットされたトランザクションによって行われた変更は確認されますが、それ以降のトランザクションまたはコミットされていないトランザクション (このトランザクション以外) によって行われた変更は確認されません。つまり、トランザクションの開始後、トランザクションによって表示されるデータは、トランザクションが開始された時点のデータであり、その後の他のトランザクションの変更は、このトランザクションでは表示されません。

一貫性のある読み取りは、RC および RR 分離レベルで SELECT ステートメントを処理する InnoDB のデフォルト モードです。一貫した非ロック読み取りでは、アクセスするテーブルにロックが設定されないため、テーブルに対して一貫した非ロック読み取りを実行している間、他のトランザクションはそれらを同時に読み取りまたは変更できます。

https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

2. 現在の読み取りとスナップショットの読み取り

現在の読み取り

は、UPDATE、DELETE、INSERT、SELECT... LOCK IN SHARE MODE、SELECT... FOR などの最新バージョンを読み取ります。 UPDATEこれらの操作はすべて現在の読み取りです。なぜ現在の読み取りと呼ばれるのでしょうか?つまり、レコードの最新バージョンを読み取ります。読み取り時には、他の同時トランザクションが現在のレコードを変更できないようにする必要があり、読み取られたレコードはロックされます。

スナップショット読み取り

スナップショット バージョン、つまり履歴バージョンを読み取ります。ロック解除された SELECT 操作はスナップショット読み取りです。ロックを行わないノンブロッキング読み取り; スナップショット読み取りの前提条件は、アンコミット読み取りは現在のトランザクションに準拠するデータ行ではなく、常に最新のデータ行を読み取るため、分離レベルが非コミット読み取りおよびシリアル化読み取りレベルではないことです。 version とシリアル化された読み取りにより、テーブル がロックされます。

3. 暗黙的ロックと明示的ロック

暗黙的ロック

InnoDB は 2 段階のロック プロトコルを使用します (アクティブ ディスプレイなし) locking):

  • ロックはいつでも実行でき、InnoDB は分離レベルに基づいて必要に応じて自動的にロックします;
  • ロックのみコミットまたはロールバック時に解放されますが実行されると、すべてのロックが同時に解除されます。

明示的なロック

  • InnoDB は、特定のステートメント (ストレージ エンジン層) を介した明示的なロックもサポートしています。

    select ... lock in share mode //共享锁
    select ... for update //排他锁
    ログイン後にコピー
  • MySQL Server レイヤーでの表示ロック:

    lock table
    unlock table
    ログイン後にコピー

上記の概念を理解した後、InnoDB トランザクションがどのように実装されるかを見てみましょう (以下)。アクティブにロックされた選択)

トランザクションは現在読み取られているデータをロックしません; トランザクションは現在、特定のデータを更新しています大量のデータ (つまり、更新が発生した瞬間) を処理するには、最初に
行レベルの共有ロック
をデータに追加する必要があります。この共有ロックは、トランザクションが終了するまで解放されません。
トランザクションは、現在読み取られているデータに 、行が読み取られると、行レベルの共有ロックはすぐに解放されます。トランザクションが特定のデータを更新した瞬間 (つまり、更新が発生した瞬間)、最初に
row を追加する必要があります。 -レベルの排他ロック
、トランザクションが終了するまで解放されません。
トランザクションが特定のデータを読み取る瞬間 (読み取りを開始する瞬間)、まずそのデータに はトランザクションが終了するまで解放されません; トランザクションが特定のデータを更新した瞬間(つまり、更新が発生した瞬間)、最初に
行レベルの排他ロックを追加する必要があります。 lock
、トランザクションが終了するまで解放されません。
トランザクションがデータを読み取るときは、トランザクションが終了するまで、まず を追加する必要があります。 トランザクションがデータを更新するときは、まず
テーブル レベルの排他ロック
を追加する必要があります。このロックはトランザクションが終了するまで解放されません。
事务隔离级别 实现方式
未提交读(RU)事务对当前被读取的数据不加锁,都是当前读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。
提交读(RC) 事务对当前被读取的数据不加锁,且是快照读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。
可重复读(RR)事务对当前被读取的数据不加锁,且是快照读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。

通过间隙锁,在这个级别MySQL就解决了幻读的问题

通过快照,在这个级别MySQL就解决了不可重复读的问题
序列化读(S) 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读

事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读问题,大大提升了数据库的并发能力。

一些常见误区

幻读到底包不包括了delete的情况?

不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的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+间隙锁解决了幻读问题,实际上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 サイトの他の関連記事を参照してください。

関連ラベル:
ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート