README
¶
Goで2相コミットにチャレンジ
概要
- Goで1相コミットと2相コミットを実装する方法を調査
- PostgreSQLとMySQLを対比させながら整理
Docker
データベースはPostgreSQLとMySQLのDockerコンテナを使用する
- PostgreSQLで2相コミットを有効化するために
max_prepared_transactionsを 1 にするmax_prepared_transactionsは同時にプリペアド状態にできるトランザクションの最大数- 最小の 1 にしておく
- 起動後の変更不可
- MySQLはデフォルトで2相コミット(分散トランザクション)が有効になっている
- PostgreSQLとMySQLとも起動オプションでクエリーログを有効化する
- 起動後に変更可
- MySQLは、この設定の影響でコンテナ起動時に接続の受付を開始するタイミングが通常よりも少し遅くなる
データベースのコンテナ起動
docker compose up -d --wait
- Docker Composeはプラグイン版
データベースのコンテナ削除
docker compose down
PostgreSQL
クエリーログの参照
クエリーログは、標準エラー(stderr)に出力される
docker logs -f pg2pc
psqlによる接続
docker container exec -it pg2pc psql -U postgres
プリペアドトランザクションの確認
docker container exec -t pg2pc psql -U postgres -c 'SELECT * FROM pg_prepared_xacts'
MySQL
クエリーログの参照
クエリーログは、general_log_fileに設定したファイルに出力される
docker container exec mysql2pc tail -f /var/lib/mysql/query.log
MySQLモニタによる接続
docker container exec -it -e MYSQL_PWD=expasswd mysql2pc mysql xadb
PREPARED 状態にある XA トランザクションの確認
docker container exec -t -e MYSQL_PWD=expasswd mysql2pc mysql xadb -e 'XA RECOVER'
テーブル
- 実行時のセットアップ処理で初期化
- 1テーブル(shop)のみ
- 主キーはデータベース側で採番
- 時刻(created_at)はデータベース側で設定
erDiagram
shop {
int id PK
string name
datetime created_at
}
- MySQLにだけ初期データを入れておく
サンプルコードの実行
go run . サンプル名
- サンプル名は大文字小文字の区別なし
例
go run . ex04tx01
1相コミット
BeginTx
- 標準ライブラリに用意されているBeginTxとCommitを使う
- PostgreSQLに1レコードINSERTして、MySQLから1レコードDELETEする
- データベース間でデータを移動させるイメージ
go run . ex04tx01
{"time":"2024-10-05T10:45:26.461878044+09:00","level":"INFO","msg":"INSERT","RowsAffected":1}
{"time":"2024-10-05T10:45:26.464105017+09:00","level":"INFO","msg":"DELETE","RowsAffected":1}
2024-10-05 10:45:26.459 JST [91] LOG: statement: begin
2024-10-05 10:45:26.461 JST [91] LOG: execute stmtcache_b5197bb703d4895f640c001d049e70c77cbe7dfe8baf65fb: INSERT INTO shop (name) VALUES ($1)
2024-10-05 10:45:26.461 JST [91] DETAIL: Parameters: $1 = 'shop1st'
2024-10-05 10:45:26.464 JST [91] LOG: statement: commit
- データベース側のクエリーログによりMySQLでは
START TRANSACTIONとCOMMITが送信されていることが確認できる- SQLドライバ依存
2024-10-05T10:45:26.459579+09:00 9 Query START TRANSACTION
2024-10-05T10:45:26.462603+09:00 9 Prepare DELETE FROM shop WHERE NAME = ?
2024-10-05T10:45:26.463305+09:00 9 Execute DELETE FROM shop WHERE NAME = 'shop1st'
2024-10-05T10:45:26.464335+09:00 9 Close stmt
2024-10-05T10:45:26.466995+09:00 9 Query COMMIT
ExecContext
- BeginTxとCommitは使わずExecContextを使ってトランザクションを制御する
- 送信するトランザクションのコマンドは、PostgreSQLは小文字表記にし、MySQLは大文字表記にする
go run . ex04tx02
{"time":"2024-10-05T10:46:23.139315578+09:00","level":"INFO","msg":"INSERT","RowsAffected":1}
{"time":"2024-10-05T10:46:23.141185775+09:00","level":"INFO","msg":"DELETE","RowsAffected":1}
- データベース側のクエリーログによりPostgreSQLはExecContextを使っても同じようにトランザクション制御できることが確認できる
2024-10-05 10:46:23.133 JST [94] LOG: statement: begin
2024-10-05 10:46:23.138 JST [94] LOG: execute stmtcache_b5197bb703d4895f640c001d049e70c77cbe7dfe8baf65fb: INSERT INTO shop (name) VALUES ($1)
2024-10-05 10:46:23.138 JST [94] DETAIL: Parameters: $1 = 'shop2nd'
2024-10-05 10:46:23.141 JST [94] LOG: statement: commit
- データベース側のクエリーログによりMySQLもExecContextを使って同じようにトランザクション制御できることが確認できる
2024-10-05T10:46:23.134741+09:00 10 Query START TRANSACTION
2024-10-05T10:46:23.139865+09:00 10 Prepare DELETE FROM shop WHERE NAME = ?
2024-10-05T10:46:23.140321+09:00 10 Execute DELETE FROM shop WHERE NAME = 'shop2nd'
2024-10-05T10:46:23.141383+09:00 10 Close stmt
2024-10-05T10:46:23.143516+09:00 10 Query COMMIT
対話型
PostgreSQL
psqlによる接続でトランザクションのコマンドを確認する
1相コミット
postgres=# begin;
BEGIN
postgres=*# INSERT INTO shop (name) VALUES ('shopclitx');
INSERT 0 1
postgres=*# commit;
COMMIT
- 大文字小文字の区別なし(ケース・インセンシティブ)
- ロールバックは
rollback
2相コミット
beginは1相コミットと同じで、commitがprepare transactionとcommit preparedの2段階になる- begin
- prepare transaction transaction_id
- commit prepared transaction_id
- transaction_id はトランザクション識別子で任意の文字列リテラル
postgres=# begin;
BEGIN
postgres=*# INSERT INTO shop (name) VALUES ('shopclixa');
INSERT 0 1
postgres=*# prepare transaction 'cli';
PREPARE TRANSACTION
postgres=# commit prepared 'cli';
COMMIT PREPARED
- ロールバックは
rollback prepared 'cli'
MySQL
MySQLモニタによる接続でトランザクションのコマンドを確認する
1相コミット
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO shop (name) VALUES ('shopclitx');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.01 sec)
- 大文字小文字の区別なし(ケース・インセンシティブ)
START TRANSACTIONはBEGINでも同じ- ロールバックは
ROLLBACK
2相コミット
- 1相コミットとは別コマンドになる。また
XA PREPAREの前にXA ENDでトランザクションをACTIVE状態からIDLE状態にしておく必要がある- XA BEGIN xid
- XA END xid
- XA PREPARE xid
- XA COMMIT xid
- xid はXAトランザクション識別子で、クライアントによって指定することもできる
- 例では64バイト以下の文字列リテラルにしておく
mysql> XA BEGIN 'cli';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO shop (name) VALUES ('shopclixa');
Query OK, 1 row affected (0.00 sec)
mysql> XA END 'cli';
Query OK, 0 rows affected (0.00 sec)
mysql> XA PREPARE 'cli';
Query OK, 0 rows affected (0.01 sec)
mysql> XA COMMIT 'cli';
Query OK, 0 rows affected (0.00 sec)
- ロールバックは
XA ROLLBACK 'cli'
2相コミット
一括
- ExecContextを使って2相コミットを制御する
- トランザクション識別子はPostgreSQL( transaction_id )とMySQL( xid )で別々にもできるが、例では同じ識別子にしておく
go run . ex04xa01
{"time":"2024-10-05T10:52:07.577642221+09:00","level":"INFO","msg":"INSERT","RowsAffected":1}
{"time":"2024-10-05T10:52:07.580293037+09:00","level":"INFO","msg":"DELETE","RowsAffected":1}
2024-10-05 10:52:07.575 JST [114] LOG: statement: begin
2024-10-05 10:52:07.576 JST [114] LOG: execute stmtcache_b5197bb703d4895f640c001d049e70c77cbe7dfe8baf65fb: INSERT INTO shop (name) VALUES ($1)
2024-10-05 10:52:07.576 JST [114] DETAIL: Parameters: $1 = 'shop3rd'
2024-10-05 10:52:07.580 JST [114] LOG: statement: prepare transaction 'shop3rd2pc'
2024-10-05 10:52:07.587 JST [114] LOG: statement: commit prepared 'shop3rd2pc'
2024-10-05T10:52:07.575527+09:00 12 Query XA BEGIN 'shop3rd2pc'
2024-10-05T10:52:07.578378+09:00 12 Prepare DELETE FROM shop WHERE NAME = ?
2024-10-05T10:52:07.579065+09:00 12 Execute DELETE FROM shop WHERE NAME = 'shop3rd'
2024-10-05T10:52:07.580479+09:00 12 Close stmt
2024-10-05T10:52:07.582945+09:00 12 Query XA END 'shop3rd2pc'
2024-10-05T10:52:07.583552+09:00 12 Query XA PREPARE 'shop3rd2pc'
2024-10-05T10:52:07.589633+09:00 12 Query XA COMMIT 'shop3rd2pc'
分離
- PREPAREの実行(セキュア状態にする)までとCOMMITの実行を別々に分ける
go run . ex04xa02
{"time":"2024-10-05T10:54:29.736760859+09:00","level":"INFO","msg":"INSERT","RowsAffected":1}
{"time":"2024-10-05T10:54:29.738495867+09:00","level":"INFO","msg":"prepare transaction"}
{"time":"2024-10-05T10:54:29.740725739+09:00","level":"INFO","msg":"DELETE","RowsAffected":1}
{"time":"2024-10-05T10:54:29.746594683+09:00","level":"INFO","msg":"XA PREPARE"}
2024-10-05 10:54:29.734 JST [123] LOG: statement: begin
2024-10-05 10:54:29.736 JST [123] LOG: execute stmtcache_b5197bb703d4895f640c001d049e70c77cbe7dfe8baf65fb: INSERT INTO shop (name) VALUES ($1)
2024-10-05 10:54:29.736 JST [123] DETAIL: Parameters: $1 = 'shop4th'
2024-10-05 10:54:29.737 JST [123] LOG: statement: prepare transaction 'shop4th2pc'
2024-10-05T10:54:29.738766+09:00 16 Query XA BEGIN 'shop4th2pc'
2024-10-05T10:54:29.739541+09:00 16 Prepare DELETE FROM shop WHERE NAME = ?
2024-10-05T10:54:29.739943+09:00 16 Execute DELETE FROM shop WHERE NAME = 'shop4th'
2024-10-05T10:54:29.740871+09:00 16 Close stmt
2024-10-05T10:54:29.741088+09:00 16 Query XA END 'shop4th2pc'
2024-10-05T10:54:29.741706+09:00 16 Query XA PREPARE 'shop4th2pc'
PostgreSQL
- psqlを使ってCOMMITする
pg_prepared_xactsビューでプリペアドトランザクションを確認できるgidがトランザクション識別子
postgres=# SELECT * FROM pg_prepared_xacts;
transaction | gid | prepared | owner | database
-------------+------------+-------------------------------+----------+----------
774 | shop4th2pc | 2024-10-05 10:54:29.737204+09 | postgres | postgres
(1 row)
postgres=# commit prepared 'shop4th2pc';
COMMIT PREPARED
MySQL
- MySQLモニタを使ってCOMMITする
XA RECOVERで PREPARED 状態にある XA トランザクションを確認できる
mysql> XA RECOVER;
+----------+--------------+--------------+------------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------------+
| 1 | 10 | 0 | shop4th2pc |
+----------+--------------+--------------+------------+
1 row in set (0.00 sec)
mysql> XA COMMIT 'shop4th2pc';
Query OK, 0 rows affected (0.00 sec)
各ステータス
コマンドラインだけで一連の流れを確認する。対象レコードを見やすくするため、最初にTRUNCATEでテーブルのレコード全削除
PostgreSQL
postgres=# TRUNCATE shop;
TRUNCATE TABLE
postgres=# begin;
BEGIN
postgres=*# INSERT INTO shop (name) VALUES ('shopclisec');
INSERT 0 1
postgres=*# SELECT * FROM shop;
id | name | created_at
----+------------+-------------------------------
2 | shopclisec | 2024-10-05 10:56:53.037752+09
(1 row)
postgres=*# prepare transaction 'sec';
PREPARE TRANSACTION
postgres=# SELECT * FROM pg_prepared_xacts;
transaction | gid | prepared | owner | database
-------------+-----+-------------------------------+----------+----------
779 | sec | 2024-10-05 10:57:09.854516+09 | postgres | postgres
(1 row)
postgres=# SELECT * FROM shop;
id | name | created_at
----+------+------------
(0 rows)
postgres=# commit prepared 'sec';
COMMIT PREPARED
postgres=# SELECT * FROM shop;
id | name | created_at
----+------------+-------------------------------
2 | shopclisec | 2024-10-05 10:56:53.037752+09
(1 row)
prepare transactionを実行するとセキュア状態になったレコードを参照できなくなり、commit preparedで再び参照できるようになるprepare transactionとcommit preparedの間ではSELECTだけでなく、INSERTなど更新も実行することができるが、その更新内容はセキュア状態にしたトランザクションに含まれることはなく、別トランザクションの扱いになる
MySQL
mysql> TRUNCATE shop;
Query OK, 0 rows affected (0.03 sec)
mysql> XA BEGIN 'sec';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO shop (name) VALUES ('shopclisec');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM shop;
+----+------------+---------------------+
| id | name | created_at |
+----+------------+---------------------+
| 1 | shopclisec | 2024-10-05 10:58:10 |
+----+------------+---------------------+
1 row in set (0.00 sec)
mysql> XA END 'sec';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM shop;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the IDLE state
mysql> XA PREPARE 'sec';
Query OK, 0 rows affected (0.01 sec)
mysql> XA RECOVER;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
| 1 | 3 | 0 | sec |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)
mysql> SELECT * FROM shop;
Empty set (0.00 sec)
mysql> XA COMMIT 'sec';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM shop;
+----+------------+---------------------+
| id | name | created_at |
+----+------------+---------------------+
| 1 | shopclisec | 2024-10-05 10:58:10 |
+----+------------+---------------------+
1 row in set (0.00 sec)
XA PREPAREを実行するとセキュア状態になったレコードを参照できなくなり、XA COMMITで再び参照できるようになるXA ENDとXA PREPAREの間はIDLE状態なのでSELECTも実行できずエラーになる
関連ドキュメント
英語
PostgreSQL 17
MySQL 8.4
日本語
PostgreSQL 16
バージョンが少し古い
MySQL 8.0
バージョンが少し古い
Documentation
¶
There is no documentation for this package.
Click to show internal directories.
Click to hide internal directories.