MySQL最適化レッスン6:Mysqlトランザクション分離レベルとロックメカニズムの詳細な理解

概要

私たちのデータベースは通常、複数のトランザクションを同時に実行し、複数のトランザクションが同じデータバッチを同時に追加、削除、変更、クエリする場合があります。これにより、ダーティライト、ダーティリード、繰り返し不可のリード、ファントムリードと呼ばれる質問が発生する可能性があります。 。これらの問題の本質は、データベースのマルチトランザクション同時実行問題です。マルチトランザクション同時実行問題を解決するために、データベースは事务隔离机制、锁机制、MVCC多版本并发控制隔离机制一連のメカニズムを使用して設計されています解决多事务并发问题

トランザクションとそのACIDプロパティ

トランザクションは、SQLステートメントのセットで構成される論理処理ユニットです。トランザクションには次の4つの属性があり、通常、トランザクションのACID属性と呼ばれます。

  • Atomicity:トランザクションは、データへの変更のすべてが実行されるか、まったく実行されないアトミックな操作単位です。
  • 一貫性:データは、トランザクションの開始時と完了時の両方で一貫性のある状態を維持する必要があります。これは、データの整合性を維持するために、関連するすべてのデータルールをトランザクションの変更に適用する必要があることを意味します。
  • 分離:データベースシステムは、トランザクションが外部の並行操作の影響を受けない独立した環境で実行されることを保証するための特定の分離メカニズムを提供します。これは、トランザクションプロセスの中間状態が外部からは見えないことを意味し、その逆も同様です。
  • 耐久性:トランザクションが完了した後、データへの変更は永続的であり、システム障害が発生した場合でも維持できます。

同時トランザクション処理の問題

更新の紛失または書き込みのダーティ

更新が失われる問題は、2つ以上のトランザクションが同じ行を選択し、最初に選択された値に基づいてその行を更新する場合に発生します。これは、各トランザクションが他のトランザクションの存在を認識していないためです。最後の更新により、他のトランザクションによって作成された値が上書きされます。更新しました。

ダーティリード

トランザクションがレコードを変更しています。トランザクションが完了してコミットされる前は、このレコードのデータは一貫性のない状態です。この時点で、別のトランザクションも同じレコードを読み取ります。制御されていない場合、これを読み取る2番目のトランザクションは「ダーティ」データとそれに基づいてさらに処理を行うと、コミットされていないデータ依存関係が作成されます。この現象は鮮やかに「脏读」と呼ばれています。

一文で:トランザクションAは、トランザクションBが変更したが、まだコミットされていないデータを読み取ります、およびこのデータベースで操作も行います。この時点で、トランザクションBがロールバックすると、Aによって読み取られたデータは無効になり、整合性要件を満たしていません。

繰り返し不可能な読み取り

トランザクションが一部のデータを読み取った後のある時点で、以前に読み取ったデータを再度読み取りますが、読み取ったデータが変更されたか、一部のレコードが削除されていることがわかります。この現象を「不可重复读」と呼びます。
一文で:トランザクションA内の同じクエリステートメントは、異なる時間に一貫性のない結果を読み取りますが、これは分離に準拠していません

ファントムリード

トランザクションは、以前に取得したデータを同じクエリ条件に従って再読み取りしますが、他のトランザクションがそのクエリ条件を満たす新しいデータを挿入していることを検出します。この現象は「幻读」と呼ばれます。

トランザクション分離レベル

「ダーティリード」、「非反復可能リード」、および「ファントムリード」はすべて数据库读一致性问题、特定のトランザクション分離メカニズムを提供するデータベースによって実際に解決されます。
ここに画像の説明を挿入
データベースのトランザクション分離が厳密であるほど、同時発生の副作用は小さくなりますが、トランザクション分離は本質的にトランザクションをある程度「シリアル化」するためであり、これは明らかに「同時実行性」と矛盾するため、コストは高くなります。
同時に、アプリケーションごとに読み取りの整合性とトランザクションの分離に関する要件が異なります。たとえば、多くのアプリケーションは「繰り返し不可の読み取り」と「ファントム読み取り」に敏感ではなく、データに同時にアクセスする機能についてより懸念する場合があります。 。

-- 看当前数据库的事务隔离级别
show variables like 'tx_isolation'; -- mysql8.0之前的写法
show variables like 'transaction_isolation'; -- mysql8.0的写法

-- 设置事务隔离级别
set tx_isolation='REPEATABLE-READ';-- mysql8.0之前的写法
set transaction_isolation='REPEATABLE-READ';-- mysql8.0的写法

Mysqlのデフォルトのトランザクション分離レベルは繰り返し読み取り可能です。Springを使用してプログラムを開発する場合、分離レベルが設定されていない場合は、Mysqlによって設定された分離レベルがデフォルトで使用されます。Springが設定されている場合は、設定されている分離レベルが使用されます。 。

ロック機構

ロックは、コンピューターが複数のプロセスまたはスレッドによるリソースへの同時アクセスを調整するメカニズムです。
データベースでは、従来のコンピューティングリソース(CPU、RAM、I / Oなど)の競合に加えて、データもユーザーが共有する必要のあるリソースです。
同時データアクセスの一貫性と有効性を確保する方法は、すべてのデータベースが解決しなければならない問題であり、ロックの競合も同時データベースアクセスのパフォーマンスに影響を与える重要な要素です。

ロック分類

  • からパフォーマンス上記は次のように分けられます:(乐观锁バージョン比較によって実装されます)と悲观锁

  • からデータベースでの操作の種類に分けられます:读锁写锁(両方とも悲観的なロックです)
    读锁(共享锁,S锁(Shared)):同じデータに対して、互いに影響を与えることなく複数の読み取り操作を同時に実行できます
    写锁(排它锁,X锁(eXclusive)):現在の書き込み操作が完了する前に、他の書き込みロックと読み取り
    ロックをブロックし、読み取りロックはブロックします書き込みますが、読み取りはブロックしません。書き込みロックは、読み取りと書き込みの両方をブロックします

  • データ操作の粒度から、テーブルロックと行ロックに分けられます。

テーブルロック

各操作はテーブル全体をロックします。オーバーヘッドが低く、ロックが高速です。デッドロックがありません。ロックの粒度が大きく、ロックの競合の可能性が最も高く、同時実行性が最も低くなります。通常、テーブル全体のデータ移行のシナリオで使用されます。

基本操作

#建表SQL(MyISAM存储引擎)
CREATE TABLE `mylock` (
  `id` INT (11) NOT NULL AUTO_INCREMENT,
  `NAME` VARCHAR (20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE = MyISAM DEFAULT CHARSET = utf8;

#插入数据
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('1', 'a');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('2', 'b');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('3', 'c');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('4', 'd');
-- 手动增加表锁:
lock table 表名称 read(write), 表名称2 read(write)-- 查看表上加过的锁:
show open tables;
-- 删除表锁:
unlock tables; 

行ロック

各操作は、データの行をロックします。オーバーヘッドが高く、ロックが遅い。デッドロックが発生します。ロックの粒度が最小で、ロックの競合の可能性が最も低く、同時実行性が最も高くなります。

InnoDBMYISAMとの2つの最大の違いがあります

  • サポートトランザクション(TRANSACTION)
  • 行レベルのロックをサポート

行ロックデモ

あるセッションはコミットせずにトランザクションの更新を開き、別のセッションは同じレコードの更新をブロックし、異なるレコードの更新はブロックしません

要約する

クエリステートメントSELECTを実行する前に、MyISAMは関連するすべてのテーブルに読み取りロックを自動的に追加し、更新、挿入、および削除操作を実行するときに関連するテーブルに書き込みロックを自動的に追加します。

InnoDBがSELECTクエリステートメントを実行すると、mvccメカニズムのためにロックされませんが、更新、挿入、および削除操作によって行ロックが追加されます

要するに、です读锁会阻塞写,但是不会阻塞读。而写机制会把读和写都阻塞

行ロックと分離レベルのケース分析

CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `account` (`name`, `balance`) VALUES ('lilei', '450');
INSERT INTO `account` (`name`, `balance`) VALUES ('hanmei','16000');
INSERT INTO `account` (`name`, `balance`) VALUES ('lucy', '2400');

コミットされていない読み取り

1.クライアントAを開き、現在のトランザクション分離レベルをコミットされていない読み取り(コミットされていない読み取り)に設定します

set tx_isolation='read-uncommitted';
set transaction_isolation = 'read-uncommitted'; -- mysql8.0

テーブルアカウントの初期値を照会します
ここに画像の説明を挿入
。2。クライアントAのトランザクションがコミットされる前に、別のクライアントBを開き、テーブルアカウントを更新します。3
ここに画像の説明を挿入
。この時点で、クライアントBのトランザクションはコミットされていませんが、クライアントAは照会できます。 Bによって更新されたデータへ
ここに画像の説明を挿入
4.何らかの理由でクライアントBのトランザクションがロールバックされると、すべての操作が取り消され、クライアントAによってクエリされたデータは実際にはダーティデータ
ここに画像の説明を挿入
になります。5。クライアントAで更新ステートメントを実行します。アカウントセットのバランスを更新=バランス-50ここでid=1;
lileiのバランスは350ではなく400になりました。データに一貫性がないのは不思議ではありませんか?如果你这样想就太天真了,在应用程序中,我们会用400-50 = 350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离机制
ここに画像の説明を挿入

コミットされた読み取り

1.クライアントAを開き、現在のトランザクション分離レベルを読み取りコミット(非コミット読み取り)に設定し、テーブルアカウントのすべてのレコードをクエリします

set transaction_isolation = 'read-committed'; -- mysql8.0

ここに画像の説明を挿入

2.クライアントAのトランザクションがコミットされる前に、別のクライアントBを開き、テーブルアカウントを更新します。3
ここに画像の説明を挿入
。この時点では、クライアントBのトランザクションはコミットされておらず、クライアントAはBによって更新されたデータを照会できません。解决了脏读问题
ここに画像の説明を挿入
4.クライアント端末Bのトランザクションが送信され
ここに画像の説明を挿入
ます。5。クライアントAが前のステップと同じクエリを実行し、結果が前のステップと矛盾します。つまり、繰り返し不可能な読み取りの問題が発生します。
ここに画像の説明を挿入

繰り返し可能な読み取り

1.クライアントAを開き、現在のトランザクションモードを繰り返し可能な読み取りに設定し、テーブルアカウントのすべてのレコードをクエリします

set transaction_isolation = 'repeatable-read';

ここに画像の説明を挿入
2.クライアントAのトランザクションがコミットされる前に、別のクライアントBを開き、テーブルアカウントを更新して送信し
ここに画像の説明を挿入
ます。3。クライアントAのテーブルアカウントのすべてのレコードをクエリします。クエリ結果は手順1と一致します。繰り返し可能な読み取り質問
ここに画像の説明を挿入
4.クライアントAで、アカウント設定の更新を実行します。balance=balance-50ここでid=1;
balanceは350-50=300にならず、lileiのバランス値はステップ2で300を使用して計算されるため、250になります。 、およびデータの整合性が損なわれることはありません。

MVCC(マルチバージョン同時実行制御)メカニズムは、反復可能な読み取り分離レベルで使用されます。select操作はバージョン番号を更新しませんが、スナップショット読み取り(履歴バージョン)です。挿入、更新、および削除はバージョン番号を更新します。 、これは現在の読み取り(現在のバージョン)です。)
ここに画像の説明を挿入
5.クライアントBを再度開き、新しいデータを挿入して送信し
ここに画像の説明を挿入
ます。6.クライアントAのアカウントテーブルのすべてのレコードをクエリします。新しいデータが見つからないため、ファントムの読み取りは行われません
ここに画像の説明を挿入
。7.ファントムの読み取り
を確認します。クライアントAで更新アカウントを実行します。setbalance=888where id = 4;
正常に更新でき、クライアントBの新しいデータは再度クエリを実行することで見つけることができます。
ここに画像の説明を挿入

シリアライズ

1.クライアントAを開き、現在のトランザクションモードをシリアル化可能に設定し、テーブルアカウントの初期値を照会します

set transaction_isolation = 'serializable';

ここに画像の説明を挿入

2.クライアントBを開き、現在のトランザクションモードをシリアル化可能に設定します。ID1の同じレコードの更新はブロックされて待機し、ID 2のレコードの更新は成功する可能性があり、シリアルモードのInnoDBクエリもブロックされることを示します。 。プラス行ロック
クライアントAが範囲クエリを実行すると、範囲内のすべての行に、レコードの各行が配置されているギャップ間隔範囲が含まれます(行データが挿入されていない場合でも、ロックされます。これがギャップです。ロック)。ロックされています。
このとき、クライアントBがこの範囲のデータを挿入するとブロックされるため、ファントム読み取りは回避されます。
这种隔离级别并发度极低,开发中很少会用到
ここに画像の説明を挿入

ギャップロック

ギャップロックは、2つの値の間のギャップをロックします。Mysqlのデフォルトレベルはrepeatable-readです。ファントム読み取りの問題を解決する方法はありますか?ギャップロックは、場合によってはファントム読み取りを解決できます。

分離レベルをmysqlのデフォルトの分離レベルに調整すると、アカウントには次のデータが含まれます。
ここに画像の説明を挿入
ギャップにはID(3,10)、(10,20)、(20、正の無限大)があります。これらの3つの間隔
はクライアントで実行されます。更新アカウントセット名='zhuge'ここで、id>8およびid<18;この
場合、他のクライアントは、この範囲に含まれるすべての行レコードおよび行レコード間のギャップにデータを挿入または変更できません。つまり、idは次のようになります。 ( 3,20]間隔はデータを変更できません。最後の20も含まれることに注意してください
间隙锁是在可重复读隔离级别下才会生效

次のキーロック

Next-key Locksは、行ロックとギャップロックの組み合わせです。上記の例の(3,20]の間隔全体は、キーロックと呼ぶことができます。

インデックスのない行ロックがテーブルロックにアップグレードされます

ロックは主にインデックスに追加されます。非インデックスフィールドが更新されると、行ロックがテーブルロックになる場合があります。

  • クライアントAは次のように実行します。アカウントセットの残高を更新=800ここで、name ='lilei';
  • クライアントBは、テーブルのすべての行をブロックします。

InnoDB行ロックはインデックスのロックであり、レコードのロックではありません。また、インデックスを無効にすることはできません。無効にしないと、行ロックからテーブルロックにアップグレードされます。

結論は

InnoDBストレージエンジンは行レベルのロックを実装しているため、ロックメカニズムのパフォーマンス消費はテーブルレベルのロックよりも高くなる可能性がありますが、全体的な同時処理機能の点でMyISAMのテーブルレベルのロックよりもはるかに優れています。

システムの同時実行性が高い場合、InnoDBの全体的なパフォーマンスには、MyISAMと比較して明らかな利点があります。

ただし、InnoDBの行レベルのロックには脆弱な側面もあります。これを不適切に使用すると、InnoDBの全体的なパフォーマンスはMyISAMのパフォーマンスより高くならない場合がありますが、さらに悪くなる可能性があります。

行ロック分析

InnoDB_row_lockステータス変数を調べて、システムの行ロックの競合を分析します

show status like 'innodb_row_lock%';

ここに画像の説明を挿入
各状態数の説明は次のとおりです。
Innodb_row_lock_current_waits:現在待機中のロックの数
Innodb_row_lock_time:システムの起動からロックがロックされるまでの合計時間:
Innodb_row_lock_time_avg各待機に費やされた平均時間
Innodb_row_lock_time_max:からの最長待機時間現在までの
Innodb_row_lock_waitsシステム起動:システム起動それ以降の合計待機時間

これらの5つの状態変数の場合、より重要なものは次のとおりです。
Innodb_row_lock_time_avg(平均待ち時間)
Innodb_row_lock_waits(合計待機時間)
Innodb_row_lock_time(総待ち時間)
特に待ち時間が多く、待ち時間が少なくない場合は、なぜシステム内で待ち時間が多いのかを分析し、分析結果に基づいて最適化計画を立てる必要があります。 。

INFORMATION_SCHEMAシステムライブラリロック関連のデータシートを表示する

--查看事务 
select * from INFORMATION_SCHEMA.INNODB_TRX;
-- innodb_locks表在8.0.13版本中由performance_schema.data_locks表所代替
--查看锁 
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
--查看锁等待
--innodb_lock_waits表则由performance_schema.data_lock_waits表代替。
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

--释放锁trx_mysql_thread_id可以从INNODB_TRX表里查看到
kill trx_mysql_thread_id;
--查看锁等待详细信息 mysql8.0去掉\G
show engine innodb status\G;

デッドロック

クライアントAの実行:select * from account where id = 1 for update;
クライアントBの実行:select * from account where id = 2 for update;
クライアントAの実行:select * from account where id = 2 for update;
クライアントBの実行:select *更新の場合はid=1のアカウントから。

最近のデッドロックログ情報を表示する:show engine innodb status;ほとんどの場合、mysqlはデッドロックを自動的に検出し、デッドロックの原因となったトランザクションをロールバックできますが、場合によってはmysqlがデッドロックを自動的に検出できないことがあります。

ロック最適化の推奨事項

  1. 可能な限り、すべてのデータ取得はインデックスを介して行われ、インデックス以外の行ロックがテーブルロックにエスカレートするのを防ぎます。
  2. ロックの範囲を最小限に抑えるようにインデックスを合理的に設計する
  3. ギャップロックを回避するために、取得条件の範囲を可能な限り最小化します。
  4. トランザクションのサイズを制御し、ロックリソースの量と時間の長さを減らし、トランザクションの最後にトランザクションロックを含むSQLを実行してみてください。
  5. 可能な限り低レベルのトランザクション分離

おすすめ

転載: blog.csdn.net/upset_poor/article/details/122982584