[前回] MySQL vs. TiDB-分散トランザクションの比較検証(11): 同時実行制御(MySQL)
はじめに
今回は分散トランザクションのデッドロック検証です。
まずは、TiDBに対し実施します。
デッドロック(Deadlock)とは
Wikipediaから、デッドロック(deadlock)の定義
- 2つ以上のスレッドあるいはプロセスなどの処理単位が、互いの処理終了を待ち
- 結果として、どの処理も先に進めなくなってしまうこと
お互い相手の処理が終わるのを延々に待ち続けてしまう状態。。。
第3者がロック解除してあげないと埒が明かなくなりそうです。
TiDBは、デッドロックをどうように対処するでしょうか。
検証シナリオ
![]() |
---|
同時に、二つの分散トランザクションから振込みボタンが押された、といったシナリオです。
この際、デッドロックが起きるメカニズムの例です。
※ 更新処理の順序と対象が肝
- 更新1: トランザクションAにより
- A銀行残高レコードから10万円引く
- レコードがロックされた状態
- 更新2: トランザクションBにより
- B銀行残高レコードから10万円引く
- レコードがロックされた状態
- 更新3: トランザクションAにより
- B銀行残高レコードに10万円足す
-
更新2
によりレコードロック中のため、その解除を待つ。。。
- 更新4: トランザクションBにより
- A銀行残高レコードに10万円足す
-
更新1
によりレコードロック中のため、その解除を待つ。。。
お互い待ち続ける状態。。。
検証スタート
- 端末を三つ用意します
- 端末1: TiDBクラスタを起動
- 端末2: トランザクションAを実行
- 端末3: トランザクションBを実行
端末1から、TiDBクラスタを起動
$ tiup playground --db 2 --pd 3 --kv 3
端末2から、検証前準備
- TiDBに接続
$ tiup client
Enterを押すと、コマンドプロンプトが表示されます。
my:root@127.0.0.1:4000=>
- A銀行のデータベースと口座テーブルを作成
銀行データベース: a_bank
口座テーブル: account
を作成し、レコードをINSERT。
ユーザー: foo
残高: 50万円
CREATE DATABASE a_bank;
USE a_bank;
CREATE TABLE account(name VARCHAR(10) PRIMARY KEY, balance DECIMAL(10,2));
INSERT INTO account VALUES('foo', 500000);
COMMIT;
- B銀行のデータベースと口座テーブルを作成
銀行データベース: b_bank
口座テーブル: account
を作成し、レコードをINSERT。
ユーザー: foo
残高: 50万円
CREATE DATABASE b_bank;
USE b_bank;
CREATE TABLE account(name VARCHAR(10) PRIMARY KEY, balance DECIMAL(10,2));
INSERT INTO account VALUES('foo', 500000);
COMMIT;
- A、B銀行残高を確認
select * from a_bank.account;
name | balance
------+-----------
foo | 500000.00
(1 row)
select * from b_bank.account;
name | balance
------+-----------
foo | 500000.00
(1 row)
端末2のセッションから、トランザクションA開始
start transaction;
START TRANSACTION
A銀行残高レコードから10万円引く
update a_bank.account set balance=balance-100000 where name='foo';
UPDATE 1
端末3から、TiDBに接続
- TiDBに接続
$ tiup client
Enterを押すと、コマンドプロンプトが表示されます。
my:root@127.0.0.1:4000=>
端末3のセッションから、トランザクションB開始
start transaction;
START TRANSACTION
B銀行残高レコードから10万円引く
update b_bank.account set balance=balance-100000 where name='foo';
UPDATE 1
端末2のセッションから、トランザクションAを続行
B銀行残高レコードに10万円足す
update b_bank.account set balance=balance+100000 where name='foo';
待ち状態。。。
トランザクションBにより、B銀行残高レコードがロック中のため、
そのロック解除待ちの状態。。。
端末3のセッションから、トランザクションBを続行
A銀行残高レコードに10万円足す
update a_bank.account set balance=balance+100000 where name='foo';
error: mysql: 1213: Deadlock found when trying to get lock; try restarting transaction
トランザクションAにより、A銀行残高レコードがロック中のため、
そのロック解除待ちの状態。。。
となり、Deadlockに陥ると思いきや、
すぐDeadlockが検出され、処理中断されました。
TiDBでは、Deadlockが起きても、延々にお互いを待ち続けることなく、
すぐエラーハンドリングしてくれました、すごい!
DEADLOCKS表から詳細情報を確認
select * from information_schema.deadlocks\G
*************************** 1. row ***************************
DEADLOCK_ID: 1
OCCUR_TIME: 2022-05-22T08:14:04.277036+09:00
RETRYABLE: 0
TRY_LOCK_TRX_ID: 433369837169541121
CURRENT_SQL_DIGEST: dc5f0aff8b925c386a79b6e33f64e79e814e2310a12f0d793fe9ab4c9c8c0eee
CURRENT_SQL_DIGEST_TEXT: update `b_bank` . `account` set `balance` = `balance` + ? where `name` = ? ;
KEY: 7480000000000000475F69800000000000000101666F6F0000000000FA
KEY_INFO: {"db_id":69,"db_name":"b_bank","table_id":71,"table_name":"account","index_id":1,"index_name":"PRIMARY","index_values":["foo"]}
TRX_HOLDING_LOCK: 433369847222763525
*************************** 2. row ***************************
DEADLOCK_ID: 1
OCCUR_TIME: 2022-05-22T08:14:04.277036+09:00
RETRYABLE: 0
TRY_LOCK_TRX_ID: 433369847222763525
CURRENT_SQL_DIGEST: 05704c1de71e0adc626cf220a7fbae005553d5ef74ed6bf44dec053cd656d474
CURRENT_SQL_DIGEST_TEXT: update `a_bank` . `account` set `balance` = `balance` + ? where `name` = ? ;
KEY: 7480000000000000435F69800000000000000101666F6F0000000000FA
KEY_INFO: {"db_id":65,"db_name":"a_bank","table_id":67,"table_name":"account","index_id":1,"index_name":"PRIMARY","index_values":["foo"]}
TRX_HOLDING_LOCK: 433369837169541121
デッドロックの詳細情報を確認できます。
- 発生時間
- ロック(待たせる側、待たされる側)
- SQL文
- レコードのキー情報
ここから、デッドロックの発生原因を特定でき、
再発防止策を立てられそうです。
終わりに
TiDBに対し、分散トランザクションのデッドロックを検証しました。
次回は、MySQLの番です。お楽しみに。