年越し準備は万全でしょうか。
我が家大晦日の夕食は、息子のねだりでスシローになりそうです。
お待たせしました、分散トランザクションの検証を続けます。
検証シナリオ
A、B銀行間での口座振替を分散トランザクションに見立てます
1. A銀行の口座からお金を引き出す
2. B銀行の口座にお金を振り込む
口座振替を上記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分散トランザクションの特性を、いくつかの側面から検証してみます。
お楽しみに。