この記事では、MySQL トランザクションの ACID 機能の実装原理について詳しく説明します (写真とテキスト)。必要な方は参考にしていただければ幸いです。 。
トランザクションは、MySQL などのリレーショナル データベースと NoSQL を区別する重要な側面であり、データの一貫性を確保するための重要な手段です。 この記事では、最初にトランザクションに関連する MySQL 基本概念を紹介し、次にトランザクションの ACID 特性を紹介し、その実装原則を分析します。
MySQL は広範囲かつ奥深いため、記事内の省略は避けられません。批判や修正は歓迎されます。
1. 基本概念
トランザクションは、データベースにアクセスして更新するプログラム実行単位です。トランザクションには、次のいずれかの SQL ステートメントが含まれます。 、またはそのどちらでもない。 MySQL はリレーショナル データベースとしてトランザクションをサポートしています。この記事は MySQL5.6 に基づいています。
まず、MySQL トランザクションの基本を確認してください。 (推奨コース: MySQL ビデオ チュートリアル)
1. 論理アーキテクチャとストレージ エンジン
示されているように上に示すように、MySQL サーバーの論理アーキテクチャは上から下に 3 つの層に分割できます。
(1) 最初の層: クライアント接続、認可認証などを処理します。
(2) 第 2 層: サーバー層。クエリ ステートメントの解析、最適化、キャッシュ、組み込み関数の実装、ストアド プロシージャなどを担当します。
(3) 3 番目のレイヤー: ストレージ エンジン。MySQL でのデータの保存と取得を担当します。 MySQLサーバー層はトランザクションを管理せず、トランザクションはストレージ エンジンによって実装されます。 トランザクションをサポートする MySQL のストレージ エンジンには、InnoDB、NDB Cluster などがあり、その中で最も広く使用されているのは InnoDB ですが、MyIsam、Memory などの他のストレージ エンジンはトランザクションをサポートしていません。
特に指定のない限り、次の記事で説明する内容は InnoDB に基づいています。
一般的な MySQL トランザクションは次のように操作されます:
start transaction; …… #一条或多条sql语句 commit;
ここで、start トランザクションはトランザクションの開始を識別し、トランザクションをコミットして書き込みます。実行結果をデータベースに保存します。 SQL ステートメントの実行に問題がある場合、ロールバックが呼び出され、正常に実行されたすべての SQL ステートメントがロールバックされます。もちろん、トランザクション内で rollback ステートメントを直接使用してロールバックすることもできます。
Autocommit
MySQL は、以下に示すように、デフォルトで自動コミット モードを使用します。
自動コミットこのモードでは、トランザクションが starttransaction によって明示的に開始されない場合、各 SQL ステートメントはコミット操作を実行するトランザクションとして扱われます。
自動コミットは次の方法でオフにできます。自動コミット パラメータは接続に固有のものであるため、ある接続のパラメータを変更しても他の接続には影響しません。
自動コミットがオフになっている場合、コミットまたはロールバックが実行され、トランザクションが終了し、別のトランザクションが開始されるまで、すべての SQL ステートメントは 1 つのトランザクション内にあります。
特別な操作
MySQL には、次のような特別なコマンドがトランザクション内で実行されると、強制的にトランザクションをコミットします。 DDL ステートメント (create table/drop table/alter/table)、lock tables ステートメントなど。
ただし、一般的に使用される select、insert、update、および delete コマンドは、トランザクションを強制的にコミットしません。
ACID は、トランザクションの 4 つの特性の尺度です:
原子性 (または不可分性)
一貫性
ACID はトランザクションが満たさなければならない条件であると言われるのではなく、むしろ、トランザクションを測定する 4 つの側面であると言った方が適切です。 ACID の特性とその実装原理については、理解を容易にするために以下で詳しく紹介しますが、導入の順序は厳密には A-C-I-D ではありません。
2. アトミック性1. 定義アトミック性とは、すべての操作が完了するか、または何も完了しない作業単位であることを意味します。トランザクション内の SQL ステートメントの実行が失敗すると、実行されたステートメントもロールバックする必要があり、データベースはトランザクション前の状態に戻ります。原子性の原理を説明する前に、まず MySQL トランザクション ログについて説明します。 MySQL ログには、バイナリ ログ、エラー ログ、クエリ ログ、スロー クエリ ログなど、さまざまな種類があります。さらに、InnoDB ストレージ エンジンは、REDO ログ (REDO ログ) と UNDO ログ (ロールバック ログ) の 2 つのトランザクション ログも提供します。 )。 REDO ログはトランザクションの耐久性を確保するために使用され、UNDO ログはトランザクションの原子性と分離の基礎となります。
アンドゥログについて話しましょう。アトミック性を実現する鍵は、トランザクションがロールバックされたときに、正常に実行されたすべての SQL ステートメントを元に戻せることです。 InnoDBロールバックは、UNDO ログに依存することで実現されます: トランザクションがデータベースを変更すると、InnoDB は対応する UNDO ログを生成します ; トランザクションの実行が失敗するか、rollback が呼び出されてトランザクションがロールバックされた場合は、アンドゥ ログ の情報を使用してデータを元の状態にロールバックできます。改造前でした。
undo ログは、SQL の実行に関連する情報を記録する論理ログです。ロールバックが発生すると、InnoDB は元に戻すログの内容に基づいて前の作業の逆を実行します。各挿入ではロールバック中に削除が実行され、各削除ではロールバック中に挿入が実行されます。ロールバック中に実行され、データを元に戻すために逆更新が実行されます。
更新操作を例に挙げます。トランザクションが更新を実行すると、生成される元に戻すログには、変更された行の主キー (どの行が変更されたかを知るため)、どの列が変更されたのかが含まれます。変更された値、および変更前後のこれらの列の値およびその他の情報を使用すると、ロールバック時にデータを更新前の状態に復元できます。
永続性とは、トランザクションが送信されると、データベースへの変更が永続的になることを意味します。後続の操作や障害が影響を与えることはありません。
redo ログと undo ログは両方とも InnoDB トランザクション ログに属します。まずはREDOログが存在する背景についてお話しましょう。
InnoDB は MySQL のストレージ エンジンであり、データはディスクに保存されます。ただし、データの読み書きに毎回ディスク IO が必要になると、効率が非常に低くなります。この目的のために、InnoDB はキャッシュ (バッファ プール) を提供します。バッファ プールにはディスク上の一部のデータ ページのマッピングが含まれており、データベースにアクセスするためのバッファとして機能します。データベースからデータを読み取るときは、最初にデータが読み込まれます。バッファ プール。 バッファ プールがプールに存在しない場合は、データベースにデータを書き込むときに、データがディスクから読み取られてバッファ プールに書き込まれ、変更されます。バッファ プール内のデータは定期的にディスクに更新されます (このプロセスは ダーティ フラッシュ と呼ばれます)。
バッファ プールを使用すると、データの読み取りと書き込みの効率が大幅に向上しますが、新たな問題も生じます。MySQL がダウンし、バッファ プール内の変更されたデータがディスクにフラッシュされていない場合、データ損失とトランザクションの耐久性は保証できません。
そこで、この問題を解決するために REDO ログが導入されました。データが変更されると、バッファー プール内のデータを変更するだけでなく、トランザクションがコミットされたときに操作も REDO ログに記録されます。 、 fsync インターフェースが呼び出され、REDO ログがディスクをフラッシュするために使用されます。 MySQL がダウンした場合、REDO ログ内のデータを読み取り、再起動時にデータベースを復元できます。 REDO ログは WAL (先行書き込みログ、先行書き込みログ) を使用し、すべての変更は最初にログに書き込まれ、次にバッファ プールに更新されるため、MySQL のダウンタイムによってデータが失われることがなくなり、耐久性が満たされます。要件。
トランザクションのコミット時に REDO ログもディスクにログを書き込む必要があるのに、バッファ プール内の変更されたデータを直接ディスクに書き込む (つまり、ダーティに書き込む) よりも高速なのはなぜでしょうか。 ?理由は主に 2 つあります。
(1) ダーティ クリーニングはランダム IO です。毎回変更されるデータの場所はランダムですが、REDO ログの書き込みは追加操作であり、シーケンシャル IO に属します。
(2) ダーティ クリーニングはデータ ページ (ページ) に基づいています。MySQL のデフォルトのページ サイズは 16 KB であり、ページ全体を書き込む必要があり、REDO ログには内容のみが含まれます。書き込み部分では、無効な IO が大幅に減少します。
MySQL には、書き込み操作を記録してデータ回復に使用できる binlog (バイナリ ログ) もあることはわかっていますが、この 2 つは基本的に同じものです。異なるもの:
(1) さまざまな機能: MySQL のダウンタイムが耐久性に影響を与えないように、クラッシュ回復に REDO ログが使用され、サーバーの持続性を確保するために、binlog が使用されます。データのポイントインタイムリカバリに基づいており、さらにマスター/スレーブレプリケーションにもbinlogが使用されます。
(2) さまざまなレベル: REDO ログは InnoDB ストレージ エンジンによって実装され、binlog は MySQL サーバー層によって実装されます (記事前半の MySQL 論理アーキテクチャの概要を参照してください)。 InnoDB と他のストレージのエンジンを同時に使用します。
(3) 内容は異なります。REDO ログは物理ログであり、内容はディスクのページに基づいています。binlog は論理ログで、内容は 1 つの SQL です。
(4) 書き込みタイミングが異なります。binlog はトランザクションがコミットされたときに書き込まれます。REDO ログの書き込みタイミングは比較的多様です。 : トランザクションのコミット時に fsync が呼び出され、REDO ログがフラッシュされます。これがデフォルトの戦略です。 innodb_flush_log_at_trx_commit パラメータを変更すると、トランザクションの耐久性は保証されません。
除了事务提交时,还有其他刷盘时机:如master thread每秒刷盘一次redo log等,这样的好处是不一定要等到commit时刷盘,commit速度大大加快。
与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。
隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作),那么隔离性的探讨,主要可以分为两个方面:
(一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
(一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性
首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。
锁机制的基本原理可以概括为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
行锁与表锁
按照粒度,锁可以分为表锁、行锁以及其他位于二者之间的锁。表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
如何查看锁信息
有多种方法可以查看InnoDB中锁的情况,例如:
select * from information_schema.innodb_locks; #锁的概况 show engine innodb status; #InnoDB整体状态,其中包括锁的情况
下面来看一个例子:
#在事务A中执行: start transaction; update account SET balance = 1000 where id = 1; #在事务B中执行: start transaction; update account SET balance = 2000 where id = 1;
此时查看锁的情况:
show engine innodb status查看锁相关的部分:
通过上述命令可以查看事务24052和24053占用锁的情况;其中lock_type为RECORD,代表锁为行锁(记录锁);lock_mode为X,代表排它锁(写锁)。
除了排它锁(写锁)之外,MySQL中还有共享锁(读锁)的概念。由于本文重点是MySQL事务的实现原理,因此对锁的介绍到此为止,后续会专门写文章分析MySQL中不同锁的区别、使用场景等,欢迎关注。
介绍完写操作之间的相互影响,下面讨论写操作对读操作的影响。
首先来看并发情况下,读操作可能存在的三类问题:
(1)脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。举例如下(以账户余额表为例):
(2)不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。举例如下:
(3)幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。举例如下:
SQL标准中定义了四种隔离级别,并规定了每种隔离级别下上述几个问题是否存在。一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。隔离级别与读问题的关系如下:
実際のアプリケーションでは、read uncommitted は同時実行中に多くの問題を引き起こしますが、パフォーマンスの向上は他の分離レベルに比べて非常に限定的であるため、あまり使用されません。 Serializableトランザクションを強制的にシリアル化すると、同時実行効率が非常に低くなり、データの整合性要件が非常に高く、同時実行が許容されない場合にのみ使用できるため、ほとんど使用されません。したがって、ほとんどのデータベース システムでは、デフォルトの分離レベルは Read Committed (Oracle などの ) または Repeatable Read (以下 # # と呼ばれます) です。 #RR)。
次の 2 つのコマンドを使用して、グローバル分離レベルとこのセッションの分離レベルを表示できます: InnoDB デフォルトの分離レベルは RR ですが、RR については後ほど説明します。 SQL 標準では、RR はファントム リードの問題を回避できませんが、InnoDB によって実装された RR はファントム リードの問題を回避することに注意してください。 5. MVCCRR は、MVCC を使用して、ダーティ リード、反復不可能な読み取り、ファントム リードなどの問題を解決します。MVCC の正式名は、Multi-Version Concurrency Control です。マルチバージョン同時実行制御プロトコル。次の例は、MVCC の特性をよく反映しています。同時に、異なるトランザクションによって読み取られるデータは異なる可能性があります (つまり、複数のバージョン)。時間 T5 では、トランザクション A とトランザクション C は異なるバージョンのデータを読み取ることができます。 #MVCC の最大の利点は、読み取りがロックされないため、読み取りと書き込みの間に競合がなく、同時実行パフォーマンスが優れていることです。 InnoDB は MVCC を実装しており、主にデータの非表示列 (マーク ビットとも呼ばれます) と Undo ログに依存して、複数のバージョンのデータが共存できます。データの非表示列には、データ行のバージョン番号、削除時刻、元に戻すログへのポインタなどが含まれます。MySQL は、データを読み取るときに非表示列を使用して、ロールバックが必要かどうかを判断し、したがって、MVCC は、非表示列の詳細な形式を拡張しません。 以下の内容は、上記のいくつかの問題と併せて個別に説明されています。 (1) ダーティリード トランザクション A が T3 タイムノードで zhangsan の残高を読み取ると、データが他のユーザーによって変更されていることがわかります。トランザクション、ステータスは未コミットです。このとき、トランザクションAが最新データを読み込んだ後、データのアンドゥログを基にロールバック操作を行い、トランザクションBが変更される前のデータを取得することでダーティリードを回避します。 (2) Non-repeatable read トランザクション A が T2 ノードで初めてデータを読み取るとき、データのバージョン番号 (データバージョン番号は行単位で記録されます)、バージョン番号が 1 であると仮定すると、トランザクション B がコミットすると、この行に記録されるバージョン番号は増加し、トランザクション A が T5 で再度データを読み取ると、それがわかります。データ(2)のバージョン番号は、最初の読み込み時に記録されたバージョン番号(1)よりも大きいため、アンドゥログに基づいてロールバック操作が実行され、バージョン番号が1のときのデータが取得されます。これにより、繰り返し可能な読み取りが実現します。 (3) ファントム読み取り InnoDB によって実装された RR は、ネクスト キー ロック メカニズムを通じてファントム読み取り現象を回避します。 #next-key lock は行ロックの一種で、レコード ロック (レコード ロック) ギャップ ロック (##) に相当します。 # ギャップロック); レコードそのものをロックするだけでなく(レコードロックの機能); 範囲をロックする(ギャップロック)のが特徴です。 #### ##関数)######。 もちろん、ここで話しているのはロック解除された読み取りです。このときのネクストキーロックは実際にはロックされておらず、読み取られたデータにマークを追加するだけです (マークの内容にはデータのバージョン番号が含まれます)。便宜上、これをネクストキーロック機構と呼びます。前の例を使用して説明してみましょう: トランザクション A が T2 ノードで初めて 0
要約すると、InnoDB によって実装された RR は、ロック メカニズム、データの非表示列、Undo ログ、ネクスト キー ロック クラスを通じてある程度の分離を実現します。ほとんどのシナリオのニーズに対応します。ただし、RR はファントム読み取りの問題を回避しますが、結局のところシリアル化可能ではなく、完全な分離を保証できないことに注意してください。以下に例を示します。これは自分で検証できます。
一貫性とは、トランザクションの実行が終了した後も、データベースの整合性制約が破壊されないことを意味します。データベースはトランザクション実行の前後で破棄されません。 正当なデータ ステータス。 データベースの整合性制約には、エンティティの整合性 (行の主キーが存在し、一意であるなど)、列の整合性 (フィールドのタイプ、サイズ、長さが満たさなければならないなど) が含まれますが、これらに限定されません。要件)、外部キー制約、およびユーザー定義の完全性(たとえば、2 つの口座の残高の合計は転送の前後で変更されない必要があります)。
2. 実装一貫性はトランザクションによって追求される最終的な目標であると言えます。前述のアトミック性、永続性、分離はすべてデータベース状態の一貫性を保証するためのものです。さらに、整合性の実装には、データベース レベルでの保証に加えて、アプリケーション レベルでの保証も必要です。 一貫性を実現するための対策には次のものが含まれます。以上がMySQL トランザクションの ACID 特性の実装原理の詳細な紹介 (画像とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。