MySQL vs. TiDB-分散トランザクションの比較検証(9): TiDBで銀行振込みのシミュレーション

##### [前回] [MySQL vs. TiDB-分散トランザクションの比較検証(8): TiDB分散トランザクションの基本動作](https://qiita.com/mingchun_zhao/items/182173b869e01777ed8d)

## はじめに

今回は、TiDB分散トランザクションの例として、銀行振込みのシミュレーションです。

## 検証シナリオ

分散トランザクションの例として、`A銀行`から`B銀行`へ振込みを行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/b2b01f9d-9c84-36bc-1b09-3afbdea55223.png)

分散トランザクションは、以下2操作で構成されます。

① `A銀行`口座から10万円引出す
② `B銀行`口座に10万円振込む

処理が二つのシステム(データベース)に分散され、
どちらか失敗しても、振込みは成立しません。

## 検証準備

### 端末を三つ用意します

端末1:　TiDBクラスタを起動
端末2:　TiDBトランザクションを実行
端末3:　別セッションからTiDBステータスを確認

### 端末1から

#### TiDBクラスタを起動

```
$ tiup playground --db 2 --pd 3 --kv 3
... ...
CLUSTER START SUCCESSFULLY, Enjoy it ^-^
To connect TiDB: mysql --comments --host 127.0.0.1 --port 4000 -u root -p (no password)
To connect TiDB: mysql --comments --host 127.0.0.1 --port 4001 -u root -p (no password)
To view the dashboard: http://127.0.0.1:2379/dashboard
PD client endpoints: [127.0.0.1:2379 127.0.0.1:2382 127.0.0.1:2384]
To view the Prometheus: http://127.0.0.1:9090
To view the Grafana: http://127.0.0.1:3000
```

### 端末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);
```

#### B銀行のデータベースとテーブルを作成

銀行データベース: `b_bank`
口座テーブル: `account`

レコードをINSERTします。

ユーザー: `foo`
残高: 0

```
CREATE DATABASE b_bank;
USE b_bank;
CREATE TABLE account(name VARCHAR(10) PRIMARY KEY, balance DECIMAL(10,2));
INSERT INTO account VALUES('foo', 0);
```

### 端末3から

#### TiDBに接続

```
$ tiup client
```

Enterを押すと、コマンドプロンプトが表示されます。

```
my:root@127.0.0.1:4000=>
```


#### A、B銀行の口座残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 500000.00 
(1 row)
```
```
SELECT * FROM b_bank.account;
 name |  balance  
------+-----------
 foo  | 0.00 
(1 row)
```

## 検証スタート

### 端末2から

#### 分散トランザクション開始

```
START TRANSACTION;
START TRANSACTION
```

#### A銀行から10万円引き出す

```
UPDATE a_bank.account SET balance=balance-100000 WHERE name='foo';
UPDATE 1
```

#### B銀行に10万円振り込む

```
UPDATE b_bank.account SET balance=balance+100000 WHERE name='foo';
UPDATE 1
```

#### トランザクションの同じセッションから、A、B銀行残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 400000.00 
(1 row)

SELECT * FROM b_bank.account;
 name |  balance  
------+-----------
 foo  | 100000.00 
(1 row)
```

コミットしていないのに、残高変わったようです(?)。

#### トランザクションモードを確認

```
SELECT @@tidb_txn_mode;
 @@tidb_txn_mode 
-----------------
 pessimistic 
(1 row)
```

悲観的モードになっています。

#### トランザクションのステータスを確認

```
SELECT * FROM information_schema.tidb_trx\G
*************************** 1. row ***************************
ID: 433178055110295555
START_TIME: 2022-05-13T20:59:02.358+09:00
CURRENT_SQL_DIGEST:
CURRENT_SQL_DIGEST_TEXT:
STATE: Idle
WAITING_START_TIME:
MEM_BUFFER_KEYS: 4
MEM_BUFFER_BYTES: 182
SESSION_ID: 407
USER: root
DB: b_bank
ALL_SQL_DIGESTS: ["f53534242526f5f17dd442bf4429823244510563047efdcc0679741ca5833ea6","df9f53eb4cb1672927cb6374dd27fa80a8124ec0186f390ea41cac113d8428ac","dc5f0aff8b925c386a79b6e33f64e79e814e2310a12f0d793fe9ab4c9c8c0eee","28eafac12d18f34fb1bd6a857b73b37fb446d04eacbc3e18834f07ac9eb3c352","e82a3bae628989f0af1c7ce80dd40ddb0eb532f0e4f245e3f07280da22509428","c4ef5a83e75b42191d7e4e51ff33f5a712b8b41e743952a668b405076ce134ac","d7815f6adab7ceebe67882038b7d29c081675c0e94751110ff74213523b6998e"]
```

Idle状態になっています。

### 端末3から
#### トランザクションとは別セッションから、A、B銀行残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 500000.00 
(1 row)

SELECT * FROM b_bank.account;
 name |  balance  
------+-----------
 foo  | 0.00 
(1 row)
```

残高が更新されていません。
分散トランザクションを開始したセッションのみ、残高が更新された理由は、
現時点で、分散トランザクションのステータスが、
第1フェーズの`下書き(Prewrite)`が終わっただけで、
第2フェーズの`コミット`はまだ行われていないからです。

### 端末2から

#### 分散トランザクションをロールバック

```
ROLLBACK;
ROLLBACK
```

#### A、B銀行残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 500000.00 
(1 row)

SELECT * FROM b_bank.account;
 name | balance 
------+---------
 foo  | 0.00 
(1 row)
```

残高が元とおりに戻りました。

#### トランザクションのステータスを確認

```
SELECT * FROM information_schema.tidb_trx\G
```

終了したので存在しません。

## 上記シナリオを再度実施し、コミット操作を確認

### 端末2から

#### 分散トランザクション開始

```
START TRANSACTION;
START TRANSACTION
```

#### A銀行から10万円引き出す

```
UPDATE a_bank.account SET balance=balance-100000 WHERE name='foo';
UPDATE 1
```

#### B銀行に10万円振込む

```
UPDATE b_bank.account SET balance=balance+100000 WHERE name='foo';
UPDATE 1
```

#### コミット

```
COMMIT;
COMMIT
```

#### A、B銀行残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 400000.00 
(1 row)

SELECT * FROM b_bank.account;
 name |  balance  
------+-----------
 foo  | 100000.00 
(1 row)
```

期待とおり口座振込みできました。
分散トランザクション成功、やったー。

#### トランザクションのステータスを確認

```
SELECT * FROM information_schema.tidb_trx\G
```

終了したので存在しません。

### 端末3から

#### トランザクションとは別セッションからも、A、B銀行残高を確認

```
SELECT * FROM a_bank.account;
 name |  balance  
------+-----------
 foo  | 400000.00 
(1 row)

SELECT * FROM b_bank.account;
 name |  balance  
------+-----------
 foo  | 100000.00 
(1 row)
```

別セッションからも、期待とおり残高更新が確認できました。

## 分散トランザクションにおける TiDB vs. MySQL

### スナップショットの取得タイミングが異なる

- TiDB
    - `BEGIN`や`START TRANSACTION`でトランザクション開始直後に
    - データベースのスナップショットを取得
- MySQL
    - `BEGIN`や`START TRANSACTION`でトランザクション開始後
    - InnoDBからデータ取得する最初の`SELECT`(`SELECT FOR UPDATE`は対象外)が実行されたら
    - スナップショットを取得

言い換えると、TiDBの下記三つのコマンドは、
- `BEGIN`
- `START TRANSACTION`
- `START TRANSACTION WITH CONSISTENT SNAPSHOT`

MySQLの下記コマンドに相当
- `START TRANSACTION WITH CONSISTENT SNAPSHOT`

### TiDBの一意制約レイジーチェックの最適化

- `Lazy check of constraints`とは
    - TiDBの楽観的トランザクションで
    - プライマリキーや一意制約のチェックを
    - COMMIT時に行う(通常のDML実行時でなく)
- メリット
    - 制約チェックをバッチ処理し
    - ネットワーク通信を減らすことで
    - パフォーマンスを向上できる
- デフォルト設定の変更
    - パラメータ`tidb_constraint_check_in_place`を`TRUE`に設定すると
    - 本機能が無効に

## 終わりに

TiDB分散トランザクション例を用いて、MySQL XAと比較しました。
次回は、分散トランザクションの同時実行制御を検証してみます。
お楽しみに。

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