- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理
- DB保存今ここ
前回は、索引を足しても返る行の集合は変わらず、変わるのは道筋だけであることを見ました。今回は「この文とこの文は、一緒に残るか一緒に消えるべきだ」という約束を、どう宣言するかを見ます。
まず結論
トランザクションは複数の文を「全部か無しか」にまとめる仕組みです。その「全部」がどこからどこまでかは、データベースが推し量るのではなく、アプリが BEGIN と COMMIT で宣言します。
途中の文が重複キーのエラーになったとき、PostgreSQL は宣言した範囲ごと捨て、MySQL は失敗した文だけを戻して続きを許します。どちらも COMMIT した範囲を全部か無しかで扱っており、違うのはエラーの後にその範囲を続けられるかどうかです。
PostgreSQL 18 で、主キー id の表に `BEGIN; INSERT (1,'alice'); INSERT (1,'bob'); INSERT (2,'carol'); COMMIT;` を流しました。2つ目の INSERT は主キー重複でエラーになります。最後に表には何行残るでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
「全部」の範囲は、誰が決めるのか
口座から口座へお金を移すなら、「引く」と「足す」の2つの更新が要ります。片方だけ残ると困ります。PostgreSQL のチュートリアルは、トランザクションの要点をこう書きます。
The essential point of a transaction is that it bundles multiple steps into a single, all-or-nothing operation.
では、どの文とどの文が同じまとまりなのか。データベースは文の中身からそれを知ることができません。アプリが BEGIN で開き、COMMIT(確定)か ROLLBACK(取り消し)で閉じて宣言します。
宣言しなければどうなるか。同じチュートリアルは If you do not issue a BEGIN command, then each individual statement has an implicit BEGIN and (if successful) COMMIT wrapped around it. と書きます。1文ずつが、それぞれ別の「全部」になります。 MySQL も既定で同じ動き(autocommit)です。
- ・BEGIN を送らなければ、既定では1文ずつが別のトランザクションになる
- ・同じ性質は ACID の A(Atomicity)と呼ばれる。C・I・D はこの先の回で扱う
名前の由来にも一言。Gray 1981 は性質を Consistency Atomicity Durability の3つで挙げ、原子性を it either happens or it does not と書きました。この論文に ACID の語はありません。Haerder と Reuter 1983 は isolation を含む4つを並べ、(ACID) と書いています。
同じ5文を PostgreSQL に流す
主キー id を持つ表 accounts に、次の5文と、確認の SELECT を流します。2つ目の INSERT は id が重複します。
BEGIN;
INSERT INTO accounts VALUES (1, 'alice');
INSERT INTO accounts VALUES (1, 'bob');
INSERT INTO accounts VALUES (2, 'carol');
COMMIT;
SELECT id, owner FROM accounts;
BEGIN;BEGININSERT INTO accounts VALUES (1, 'alice');INSERT 0 1INSERT INTO accounts VALUES (1, 'bob');ERROR: duplicate key value violates unique constraint "accounts_pkey"DETAIL: Key (id)=(1) already exists.INSERT INTO accounts VALUES (2, 'carol');ERROR: current transaction is aborted, commands ignored until end of transaction blockCOMMIT;ROLLBACKSELECT id, owner FROM accounts;id | owner----+-------(0 rows)psql exit=0
重複のエラーの後、carol の INSERT は current transaction is aborted で無視されました。COMMIT に対するサーバの応答は ROLLBACK で、表は0行です。alice も残っていません。宣言した範囲の一部が失敗したので、範囲ごと捨てられました。
psql 自体は最後まで流し、終了コードは0です。スクリプトが成功したかを終了コードで見ても、この取り消しには気づけません。
PostgreSQL で `COMMIT;` を送ったのに、応答が `ROLLBACK` でした。何が起きたと読むのが正しいですか。
失敗の後、範囲を続けられるかは DB が決める
PostgreSQL の用語集は原子性を either all its operations complete as a single unit or none do と定めます。では「失敗した文があれば必ず範囲ごと捨てる」のが原子性か、というとそうではありません。MySQL の公式文書はこう書きます。
InnoDB sometimes rolls back only the statement that failed, other times it rolls back the entire transaction.
重複キーについては A duplicate-key error rolls back the SQL statement, if you have not specified the IGNORE option in your statement. 戻るのは失敗した文だけで、トランザクションは開いたまま続きます。
そこでアプリが何を送るかで結果が決まります。ROLLBACK rolls back the current transaction, canceling its changes.(同じく公式文書)なので、ROLLBACK を送れば範囲全体が消えます。COMMIT を送れば、その時点で残っている変更全体がまとめて確定します。 COMMIT の場合に確定するのは、範囲に残っている変更の全体です。
同じ5文を MySQL に流す
同じファイルを MySQL 8.4 へ流します。mysql クライアントは既定で最初のエラーで止まるので、まず --force(Continue even if an SQL error occurs.)を付けて最後まで送ります。エラーの後に ROLLBACK ではなく COMMIT を送った場合です。
BEGININSERT INTO accounts VALUES (1, 'alice')INSERT INTO accounts VALUES (1, 'bob')ERROR 1062 (23000) at line 3: Duplicate entry '1' for key 'accounts.PRIMARY'INSERT INTO accounts VALUES (2, 'carol')COMMITSELECT id, owner FROM accounts+----+-------+| id | owner |+----+-------+| 1 | alice || 2 | carol |+----+-------+mysql exit=0
bob の文だけが戻り、alice と carol が確定しました。--force を付けないと結果が変わります。
BEGININSERT INTO accounts VALUES (1, 'alice')INSERT INTO accounts VALUES (1, 'bob')ERROR 1062 (23000) at line 3: Duplicate entry '1' for key 'accounts.PRIMARY'mysql exit=1--- new session:SELECT COUNT(*) AS n FROM accounts+---+| n |+---+| 0 |+---+mysql exit=0
クライアントが止まって COMMIT は送られず、0行でした。公式文書が書くのは autocommit を無効にしたセッションの場合(If a session that has autocommit disabled ends without explicitly committing the final transaction, MySQL rolls back that transaction.)で、BEGIN で始めた今回の場合を直接書いた文は見つかっていません。0行は実測です。
MySQL 8.4 で同じ5文を `--force` 付きで流し、エラーの後も COMMIT を送ったところ alice と carol が残りました。この結果の読み方として適切なのはどれですか。
データベースのトランザクション
境界の印は、アプリが送る BEGIN と COMMIT です。途中でエラーが出たとき範囲をどう扱うかは DB によって違いますが、確定は COMMIT が受け付けられたときだけです。
場面 結果PG: 重複キー後に COMMIT 応答は ROLLBACK、0行MySQL: 重複キー後に COMMIT 失敗文だけ戻り、2行確定MySQL: COMMIT 前に切断 0行
ext4 のジャーナル(別の図解で扱う)
ファイルシステムは複数のブロック更新を1つのトランザクションにまとめてジャーナルに書き、最後にコミットブロックを書きます(既定の data=ordered でジャーナルを通るのはメタデータのみ)。再起動時、コミットブロックがあれば本体へ再適用し、無ければ捨てます。
場面 結果コミットブロックが届いた 再起動時に本体へ再適用コミットブロックが届く前に停止 以降を破棄
どちらも「最後の印」で全部か無しかを決めています。では、どこまでが一まとまりかを知っているのは、それぞれ誰でしょうか。
どのブロック更新を一まとまりにするかを決めるのは誰か。alice と carol の登録が1つの用事かを知っているのは誰か。
この章のまとめ
- トランザクションは複数の文を 全部か無しか にまとめる。範囲は アプリが BEGIN と COMMIT で宣言する。BEGIN を送らなければ1文ずつが別の範囲になる。
- PostgreSQL は途中のエラーで範囲が中断状態になり、COMMIT への応答が ROLLBACK になる。表は0行。
- MySQL は重複キーで 失敗した文だけ を戻す。ROLLBACK ではなく COMMIT を送った場合、残りが確定する。ROLLBACK を送れば全部が消える。
- mysql クライアントは既定で最初のエラーで止まる。COMMIT が送られず、実測では0行。
- Gray 1981 の性質は3つで ACID の語は無い。4つを並べて (ACID) と書いたのは Haerder と Reuter 1983。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
ACID という語について、原典で確かめられる記述はどれですか。
用語集(4語)
- トランザクション
- 複数の文を1つのまとまりとして扱う単位。PostgreSQL のチュートリアルは `a single, all-or-nothing operation` と書く。
- BEGIN / COMMIT / ROLLBACK
- まとまりの開始、確定、取り消し。BEGIN を送らなければ、既定では1文ずつが別のトランザクションになる。
- 原子性 (Atomicity)
- トランザクションの操作が全部完了するか、どれも完了しないかのどちらかになる性質。ACID の A。
- ACID
- Atomicity・Consistency・Isolation・Durability の頭文字。この4つを並べて (ACID) と書いた論文に Haerder と Reuter 1983 がある。
出典(一次ソース)
- PostgreSQL 18 Documentation — 3.4. Transactions ↗
- PostgreSQL 18 Documentation — Appendix M. Glossary ↗
- MySQL 8.4 Reference Manual — 17.20.5 InnoDB Error Handling ↗
- MySQL 8.4 Reference Manual — 15.3.1 START TRANSACTION, COMMIT, and ROLLBACK Statements ↗
- MySQL 8.4 Reference Manual — 17.7.2.2 autocommit, Commit, and Rollback ↗
- MySQL 8.4 Reference Manual — 6.5.1.1 mysql Client Options ↗
- Jim Gray, The Transaction Concept: Virtues and Limitations, Tandem TR 81.3, June 1981 ↗
- T. Haerder, A. Reuter, Principles of Transaction-Oriented Database Recovery, ACM Computing Surveys 15(4), 1983 ↗