TiDB 検証ライフ(9-分散トランザクションの検証1)

年越し準備は万全でしょうか。
我が家大晦日の夕食は、息子のねだりでスシローになりそうです。

お待たせしました、分散トランザクションの検証を続けます。

## 検証シナリオ

### A、B銀行間での口座振替を分散トランザクションに見立てます

1.　A銀行の口座からお金を引き出す
2.　B銀行の口座にお金を振り込む
![スクリーンショット 2021-12-30 20.42.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61975/4eb3c9b4-7d95-4460-71ba-66fdff385a1c.png)
口座振替を上記2つのオペレーションによる1セットとみなすと、
A銀行とB銀行二つのシステム(データベース)に処理が分散され、
どちらか一方だけでは振替が成立せず、分散トランザクションとみることができます。

## 検証準備

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

端末1:　TiDBクラスタを起動
端末2:　トランザクションを実行
端末3:　結果やステータスを確認

### 端末1から

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

```
$ tiup playground --db 2 --pd 3 --kv 3
```

### 端末2から

#### TiDBに接続

```
$ tiup client
```

#### A銀行データベースa_bank、口座テーブルaccountを作成

```
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を作成

```
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
```


#### 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)
```

#### トランザクションのステータスを確認すると、Idle状態で存在しているようです

```
select * from information_schema.tidb_trx\G
*************************** 1. row ***************************
ID: 430153886054744065
START_TIME: 2021-12-31T08:27:32.931+09:00
CURRENT_SQL_DIGEST: 
CURRENT_SQL_DIGEST_TEXT: 
STATE: Idle
WAITING_START_TIME: 
MEM_BUFFER_KEYS: 2
MEM_BUFFER_BYTES: 82
SESSION_ID: 7
USER: root
DB: b_bank
ALL_SQL_DIGESTS: ["f53534242526f5f17dd442bf4429823244510563047efdcc0679741ca5833ea6","df9f53eb4cb1672927cb6374dd27fa80a8124ec0186f390ea41cac113d8428ac","dc5f0aff8b925c386a79b6e33f64e79e814e2310a12f0d793fe9ab4c9c8c0eee","28eafac12d18f34fb1bd6a857b73b37fb446d04eacbc3e18834f07ac9eb3c352","e82a3bae628989f0af1c7ce80dd40ddb0eb532f0e4f245e3f07280da22509428","c4ef5a83e75b42191d7e4e51ff33f5a712b8b41e743952a668b405076ce134ac","d7815f6adab7ceebe67882038b7d29c081675c0e94751110ff74213523b6998e"]
```

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

なんだ、コミットされていたのではなく、下書き(Prewrite)されていただけか。

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

### 気を取り直し、再度正常系の振替を実施

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

```
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とMySQLの差異に注意

トランザクション処理において、TiDBと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の楽観的トランザクションで、プライマリキーや一意制約のチェックタイミングは、DML実行時でなく、`COMMIT`時に行われる、がデフォルト設定のようです。

詳細は、PingCAP社のドキュメントをご参照ください。
https://docs.pingcap.com/tidb/stable/transaction-overview

## 終わりに

SQL操作からは、TiDBの方がMySQLのXAトランザクションに比べ、シンプルのようです。
次回は、TiDB分散トランザクションの特性を、いくつかの側面から検証してみます。
お楽しみに。