MySQL vs. TiDB-分散トランザクションの比較検証(12): デッドロック(TiDB)

##### [前回] [MySQL vs. TiDB-分散トランザクションの比較検証(11): 同時実行制御(MySQL)](https://qiita.com/mingchun_zhao/items/c8b0cac8fa9d7d647c45)

## はじめに
今回は分散トランザクションのデッドロック検証です。
まずは、TiDBに対し実施します。

## デッドロック(Deadlock)とは

Wikipediaから、[デッドロック(deadlock)の定義](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%83%E3%83%89%E3%83%AD%E3%83%83%E3%82%AF)

- 2つ以上のスレッドあるいはプロセスなどの処理単位が、互いの処理終了を待ち
- 結果として、どの処理も先に進めなくなってしまうこと

お互い相手の処理が終わるのを延々に待ち続けてしまう状態。。。
第3者がロック解除してあげないと埒が明かなくなりそうです。
TiDBは、デッドロックをどうように対処するでしょうか。

## 検証シナリオ

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/18f6d8a5-40a3-08fa-68fa-e10f1f1037cf.png)|
|--|

同時に、二つの分散トランザクションから振込みボタンが押された、といったシナリオです。
この際、デッドロックが起きるメカニズムの例です。
※ 更新処理の順序と対象が肝

```plantuml
@startuml
actor "トランザクションA" as TA #lightskyblue
database "A銀行残高レコード" as R1 #hotpink
database "B銀行残高レコード" as R2 #hotpink
actor "トランザクションB" as TB #lightskyblue

TA -> R1 : 「更新1」開始、ロック成功
activate R1
TB -> R2 : 「更新2」開始、ロック成功
activate R2
TA ->x R2 : 「更新3」開始、ロック失敗、「更新2」のロック解除待ち
TB ->x R1 : 「更新4」開始、ロック失敗、「更新1」のロック解除待ち
@enduml
```

- 更新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の番です。お楽しみに。

##### [次回] [MySQL vs. TiDB-分散トランザクションの比較検証(13(最終回)): デッドロック(MySQL)](https://qiita.com/mingchun_zhao/items/78bde883b45d23514300)