データベースを操作する同時実行制御は、データの整合性を損なうことなくデータベーストランザクションが同時に実行されることを保証する概念です。
この概念とそれを実現する方法には多くの理論とさまざまなアプローチがありますが、PostgreSQLとMySQL(InnoDBを使用する場合)がそれを処理する方法と、高度に並行するシステムで発生する可能性のある一般的な問題について簡単に説明します。デッドロック。
これらのエンジンは、MVCC(Multiversion Concurrency Control)と呼ばれる方法を使用して同時実行制御を実装します。この方法では、アイテムが更新されているときに、変更によって元のデータが上書きされることはありませんが、代わりに、アイテムの新しいバージョン(変更を含む)が作成されます。したがって、アイテムのいくつかのバージョンが保存されます。
このモデルの主な利点の1つは、データのクエリ(読み取り)用に取得されたロックがデータの書き込み用に取得されたロックと競合しないことです。したがって、読み取りによって書き込みがブロックされることはなく、書き込みによって読み取りがブロックされることもありません。
しかし、同じアイテムの複数のバージョンが保存されている場合、トランザクションはどのバージョンを参照しますか?その質問に答えるには、トランザクション分離の概念を確認する必要があります。トランザクションは、1つのトランザクションを、他のトランザクションによって行われたリソースまたはデータの変更から分離する必要がある程度を定義する分離レベルを指定します。この程度は、トランザクションによって生成されるロックに直接関連しているため、トランザクションレベルで指定できるため、実行中のトランザクションが他の実行中のトランザクションに与える影響を判断できます。
これは非常に興味深く長いトピックですが、このブログではあまり詳しく説明しません。このトピックの詳細については、PostgreSQLとMySQLの公式ドキュメントをお勧めします。
では、デッドロックに対処するときに、なぜ上記のトピックに入るのですか? SQLコマンドはMVCCの動作を保証するために自動的にロックを取得し、取得されるロックの種類は定義されたトランザクション分離に依存するためです。
ロックにはいくつかの種類がありますが(PostgreSQLとMySQLについて確認するための、もう1つの長くて興味深いトピックです)、それらについて重要なことは、それらが互いにどのように相互作用するか(最も正確には、どのように競合するか)です。何故ですか? 2つのトランザクションは、同じオブジェクトで同時に競合するモードのロックを保持できないためです。また、マイナーでない詳細が取得されると、通常、トランザクションが終了するまでロックが保持されます。
これは、ロックタイプが互いにどのように競合するかを示すPostgreSQLの例です。
PostgreSQLロックタイプの競合そしてMySQLの場合:
MySQLロックタイプの競合X=排他的ロックIX=意図的排他的ロック
S=共有ロックIS=意図的な共有ロック
では、同じオブジェクトで同時に競合するロックを保持したい2つの実行中のトランザクションがある場合はどうなりますか?そのうちの1つはロックを取得し、もう1つは待機する必要があります。
これで、デッドロック中に何が起こっているのかを真に理解できるようになりました。
では、デッドロックとは何ですか?ご想像のとおり、データベースのデッドロックにはいくつかの定義がありますが、その単純さのために次のようにしています。
データベースのデッドロックは、2つ以上のトランザクションが互いにロックを放棄するのを待っている状況です。
したがって、たとえば、次の状況ではデッドロックが発生します。
デッドロックの例ここで、アプリケーションAは、更新を行うためにテーブル1の行1をロックします。
同時に、アプリケーションBはテーブル2の行2をロックします。
ここで、アプリケーションAは、実行を続行してトランザクションを終了するために、テーブル2の行2のロックを取得する必要がありますが、アプリケーションBによって保持されているため、ロックを取得できません。アプリケーションAは、アプリケーションBがロックを解放するのを待つ必要があります。 。
ただし、アプリケーションBは、実行を続行してトランザクションを終了するために、テーブル1の行1をロックする必要がありますが、アプリケーションAによって保持されているため、ロックを取得できません。
したがって、ここではデッドロック状態にあります。アプリケーションAは終了するためにアプリケーションBが保持しているリソースを待機しており、アプリケーションBはアプリケーションAが保持しているリソースを待機しています。では、どのように続行しますか?データベースエンジンはデッドロックを検出し、一方のトランザクションを強制終了し、もう一方のトランザクションのブロックを解除して、強制終了されたトランザクションでデッドロックエラーを発生させます。
PostgreSQLとMySQLのデッドロックの例をいくつか確認してみましょう:
PostgreSQL
世界の国々からの情報を含むテストデータベースがあるとします。
world=# SELECT code,region,population FROM country WHERE code IN ('NLD','AUS');
code | region | population
------+---------------------------+------------
NLD | Western Europe | 15864000
AUS | Australia and New Zealand | 18886000
(2 rows)
データベースに変更を加えたいセッションが2つあります。
最初のセッションでは、NLDコードの地域フィールドとAUSコードの人口フィールドを変更します。
2番目のセッションでは、AUSコードの地域フィールドとNLDコードの人口フィールドを変更します。
テーブルデータ:
code: NLD
region: Western Europe
population: 15864000
code: AUS
region: Australia and New Zealand
population: 18886000
セッション1:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Europe' WHERE code='NLD';
UPDATE 1
セッション2:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';
セッション2は、セッション1が終了するのを待ってハングします。
セッション1:
world=# UPDATE country SET population=18886001 WHERE code='AUS';
ERROR: deadlock detected
DETAIL: Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,15) in relation "country"
ここにデッドロックがあります。システムがデッドロックを検出し、セッション1を強制終了しました。
セッション2:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';
UPDATE 1
また、デッドロックが検出され、セッション1が強制終了された(つまり、ロックが解除された)後、2番目のセッションが正しく終了したことを確認できます。
詳細については、PostgreSQLサーバーのログをご覧ください。
2018-05-16 12:56:38.520 -03 [1181] ERROR: deadlock detected
2018-05-16 12:56:38.520 -03 [1181] DETAIL: Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
Process 1181: UPDATE country SET population=18886001 WHERE code='AUS';
Process 1148: UPDATE country SET population=15864001 WHERE code='NLD';
2018-05-16 12:56:38.520 -03 [1181] HINT: See server log for query details.
2018-05-16 12:56:38.520 -03 [1181] CONTEXT: while updating tuple (0,15) in relation "country"
2018-05-16 12:56:38.520 -03 [1181] STATEMENT: UPDATE country SET population=18886001 WHERE code='AUS';
2018-05-16 12:59:50.568 -03 [1181] ERROR: current transaction is aborted, commands ignored until end of transaction block
ここでは、デッドロックで検出された実際のコマンドを確認できます。
今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードするMySQL
MySQLでデッドロックをシミュレートするには、次のようにします。
PostgreSQLと同様に、俳優や映画などの情報を含むテストデータベースがあるとします。
mysql> SELECT first_name,last_name FROM actor WHERE actor_id IN (1,7);
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| PENELOPE | GUINESS |
| GRACE | MOSTEL |
+------------+-----------+
2 rows in set (0.00 sec)
データベースに変更を加えたいプロセスが2つあります。
最初のプロセスは、actor_id 1のフィールドfirst_nameと、actor_id7のフィールドlast_nameを変更します。
2番目のプロセスは、actor_id 7のフィールドfirst_nameと、actor_id1のフィールドlast_nameを変更します。
テーブルデータ:
actor_id: 1
first_name: PENELOPE
last_name: GUINESS
actor_id: 7
first_name: GRACE
last_name: MOSTEL
セッション1:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='GUINESS' WHERE actor_id='1';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
セッション2:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';
セッション2は、セッション1が終了するのを待ってハングします。
セッション1:
mysql> UPDATE actor SET last_name='GRACE' WHERE actor_id='7';
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
ここにデッドロックがあります。システムがデッドロックを検出し、セッション1を強制終了しました。
セッション2:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';
Query OK, 1 row affected (8.52 sec)
Rows matched: 1 Changed: 1 Warnings: 0
エラーでわかるように、PostgreSQLで見たように、両方のプロセス間にデッドロックがあります。
詳細については、コマンドSHOW ENGINE INNODB STATUS \ G:
を使用できます。mysql> SHOW ENGINE INNODB STATUS\G
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-05-16 18:55:46 0x7f4c34128700
*** (1) TRANSACTION:
TRANSACTION 1456, ACTIVE 33 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 54, OS thread handle 139965388506880, query id 15876 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1456 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 0000000005af; asc ;;
2: len 7; hex 2d000001690110; asc - i ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z ;;
*** (2) TRANSACTION:
TRANSACTION 1455, ACTIVE 47 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 53, OS thread handle 139965267871488, query id 16013 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 0000000005af; asc ;;
2: len 7; hex 2d000001690110; asc - i ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap waiting
Record lock, heap no 202 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc ;;
1: len 6; hex 0000000005b0; asc ;;
2: len 7; hex 2e0000016a0110; asc . j ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afca8c1; asc Z ;;
*** WE ROLL BACK TRANSACTION (2)
「LATESTDETECTEDDEADLOCK」というタイトルで、デッドロックの詳細を確認できます。
mysqlエラーログでデッドロックの詳細を確認するには、データベースでオプションinnodb_print_all_deadlocksを有効にする必要があります。
mysql> set global innodb_print_all_deadlocks=1;
Query OK, 0 rows affected (0.00 sec)
MySQLログエラー:
2018-05-17T18:36:58.341835Z 12 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2018-05-17T18:36:58.341869Z 12 [Note] InnoDB:
*** (1) TRANSACTION:
TRANSACTION 1812, ACTIVE 42 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 11, OS thread handle 140515492943616, query id 8467 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
2018-05-17T18:36:58.341945Z 12 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1812 lock_mode X locks rec but not gap waiting
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 000000000713; asc ;;
2: len 7; hex 330000016b0110; asc 3 k ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z ;;
2018-05-17T18:36:58.342347Z 12 [Note] InnoDB: *** (2) TRANSACTION:
TRANSACTION 1811, ACTIVE 65 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 12, OS thread handle 140515492677376, query id 9075 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
2018-05-17T18:36:58.342409Z 12 [Note] InnoDB: *** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc ;;
1: len 6; hex 000000000713; asc ;;
2: len 7; hex 330000016b0110; asc 3 k ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z ;;
2018-05-17T18:36:58.342793Z 12 [Note] InnoDB: *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap waiting
Record lock, heap no 205 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc ;;
1: len 6; hex 000000000714; asc ;;
2: len 7; hex 340000016c0110; asc 4 l ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afdcba0; asc Z ;;
2018-05-17T18:36:58.343105Z 12 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)
デッドロックが発生する理由について上記で学んだことを考慮すると、デッドロックを回避するためにデータベース側でできることはあまりないことがわかります。とにかく、DBAとして、実際にそれらをキャッチし、分析し、開発者にフィードバックを提供することが私たちの義務です。
実際には、これらのエラーは各アプリケーションに固有であるため、1つずつ確認する必要があり、これをトラブルシューティングする方法を説明するガイドはありません。これを念頭に置いて、あなたが探すことができるいくつかのことがあります。
デッドロックを調査および回避するためのヒント
実行時間の長いトランザクションを検索します。通常、ロックはトランザクションが終了するまで保持されるため、トランザクションが長くなるほど、リソースに対するロックも長くなります。可能であれば、実行時間の長いトランザクションをより小さく/より高速なトランザクションに分割してみてください。
トランザクションを実際に分割できない場合もあるため、作業は毎回一貫した順序でこれらの操作を実行することに焦点を当てる必要があります。これにより、トランザクションは明確に定義されたキューを形成し、デッドロックしなくなります。
提案できる回避策の1つは、デッドロックが発生した場合にアプリケーションが同じコマンドを再度実行するように、アプリケーションに再試行ロジックを追加することです(もちろん、根本的な問題を最初に解決してみてください)。
使用されている分離レベルを確認してください。場合によっては、それらを変更してみてください。 SELECTFORUPDATEやSELECTFORSHAREなどのコマンドを探して、明示的なロックを生成し、それらが本当に必要かどうか、またはデータの古いスナップショットで作業できるかどうかを評価します。これらのコマンドを削除できない場合に試すことができることの1つは、READCOMMITTEDなどのより低い分離レベルを使用することです。
もちろん、常に適切に選択されたインデックスをテーブルに追加してください。次に、クエリでスキャンするインデックスレコードの数を減らし、その結果、ロックの設定を減らす必要があります。
より高いレベルでは、DBAとして、一般的にロックを最小限に抑えるためにいくつかの予防策を講じることができます。一例を挙げると、この場合PostgreSQLの場合、列を追加するのと同じコマンドでデフォルト値を追加することを回避できます。テーブルを変更すると、非常に積極的なロックが発生します。デフォルト値を設定すると、null値を持つ既存の行が実際に更新されるため、この操作に非常に時間がかかります。したがって、この操作をいくつかのコマンドに分割し、列を追加し、デフォルトを追加し、null値を更新すると、ロックの影響を最小限に抑えることができます。
もちろん、DBAが実践で得られるこのようなヒントはたくさんあります(インデックスを同時に作成する、pkを追加する前にpkインデックスを個別に作成するなど)が、重要なことは、この「方法」を学び、理解することです。考えて」そして常に私たちが行っている操作のロックへの影響を最小限に抑えるために。
概要
うまくいけば、このブログはデータベースのデッドロックとそれらを克服する方法に関する有益な情報を提供してくれました。デッドロックを回避する確実な方法はないため、デッドロックがどのように機能するかを知っていると、データベースインスタンスに害を及ぼす前にデッドロックをキャッチするのに役立ちます。 ClusterControlのようなソフトウェアソリューションは、データベースの状態を常に維持するのに役立ちます。 ClusterControlはすでに何百もの企業を支援してきましたが、次はあなたですか?今すぐClusterControlの無料試用版をダウンロードして、データベースのニーズに適しているかどうかを確認してください。