タイトル: COMMIT (トランザクション制御)
SEOタイトル: SQL COMMIT 文 完全ガイド(暗黙コミット / Two-Phase Commit / 言語別 commit())
| この記事の要点 |
|
COMMIT とは
COMMIT はトランザクション中に行った変更をDBに永続化し、他のセッションから見えるようにする命令です。COMMIT 前の変更は自分のセッションでしか見えず、ROLLBACK で無効化できます。
基本構文
-- 標準SQL / MySQL / PostgreSQL
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 確定
-- SQL Server
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT TRANSACTION; -- COMMIT TRAN でも可
-- Oracle
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- BEGIN は不要、DML 開始時に暗黙開始
-- COMMIT WORK は標準SQLの古い書き方(同義)
COMMIT WORK;
明示 COMMIT と暗黙 COMMIT
| 種類 | 発生条件 | 例 |
|---|---|---|
| 明示 COMMIT | アプリが COMMIT を実行 | COMMIT; |
| 暗黙 COMMIT (DDL) | DDL 実行時に自動 | CREATE / ALTER / DROP / TRUNCATE |
| 暗黙 COMMIT (制御) | セッション終了時 | 正常切断 (autocommit ON 時) |
| 暗黙 COMMIT (管理) | SET autocommit=1 等 | SET 文一部 / LOCK TABLES (MySQL) |
DDL の暗黙コミットの落とし穴
BEGIN;
INSERT INTO logs(msg) VALUES ('start');
CREATE TABLE tmp (id INT); -- ★ MySQL/Oracle ではここで暗黙 COMMIT
INSERT INTO logs(msg) VALUES ('mid');
ROLLBACK; -- もう start を消せない
-- PostgreSQL なら DDL もロールバック可能
BEGIN;
CREATE TABLE tmp (id INT);
ROLLBACK; -- tmp テーブルも消える
Autocommit と SET AUTOCOMMIT=0
-- MySQL
SELECT @@autocommit; -- 1 (ON) / 0 (OFF)
SET autocommit = 0; -- 明示 COMMIT モード
UPDATE accounts SET balance = 0 WHERE id = 1;
-- まだ COMMIT されていない。別セッションからは見えない
COMMIT; -- ここで初めて永続化
-- セッション内だけで OFF
SET SESSION autocommit = 0;
-- PostgreSQL の psql 対話モード
\set AUTOCOMMIT off
エラー時の自動ロールバック
多くのDBは、トランザクション中にエラーが発生すると以降のステートメントを拒否します。COMMIT を実行しても ROLLBACK 扱いになります(DB別に挙動差あり)。
-- PostgreSQL: 1つでもエラーが出るとトランザクションは ABORT 状態
BEGIN;
INSERT INTO users(id, name) VALUES (1, 'taro');
INSERT INTO users(id, name) VALUES (1, 'jiro'); -- 主キー重複エラー
SELECT 1; -- ERROR: current transaction is aborted
COMMIT; -- 実質 ROLLBACK
-- MySQL: エラーが出た文だけ無効。他の文は生きる
-- → 明示的に ROLLBACK 推奨
Two-Phase Commit (XA トランザクション)
複数のDBを1つの論理トランザクションとして扱うための仕組み。第1段階で全DBに「コミットできるか」を確認 (PREPARE)、全員OKなら第2段階で実際の COMMIT を実行します。
-- MySQL XA (例)
XA START 'tx1';
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
XA END 'tx1';
XA PREPARE 'tx1'; -- 第1段階: コミット可能性を保証
-- 別のDBでも同様の PREPARE を実行
XA COMMIT 'tx1'; -- 第2段階: 確定
-- 失敗時は XA ROLLBACK 'tx1';
-- Java EE / Spring の JTA で透明に利用可能
言語別 commit() メソッド
Java JDBC
Connection conn = DriverManager.getConnection(url, user, pass);
conn.setAutoCommit(false); // 明示 COMMIT モード
try {
PreparedStatement ps = conn.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE id = ?");
ps.setBigDecimal(1, new BigDecimal("100"));
ps.setLong(2, 1L);
ps.executeUpdate();
conn.commit(); // 確定
} catch (SQLException e) {
conn.rollback(); // 巻き戻し
throw e;
} finally {
conn.close();
}
PHP PDO
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->beginTransaction();
try {
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
->execute([100, 1]);
$pdo->commit(); // 確定
} catch (\Throwable $e) {
$pdo->rollBack();
throw $e;
}
Python (psycopg2 / sqlite3 / mysql-connector)
import psycopg2
conn = psycopg2.connect(dsn)
conn.autocommit = False # 既定で False
cur = conn.cursor()
try:
cur.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (100, 1))
conn.commit() # 確定
except Exception:
conn.rollback()
raise
finally:
cur.close()
conn.close()
COMMIT のパフォーマンスチューニング
- 毎行 COMMIT は遅い: 1000 行 INSERT で 1000 fsync が走る
- バッチ COMMIT: 1000 行ごとに 1 COMMIT などで桁違いに高速
- WAL fsync を緩める(PostgreSQL
synchronous_commit=off)はクラッシュ時に最後の数秒消える覚悟が必要 - MySQL
innodb_flush_log_at_trx_commitも同様のトレードオフ(1: 安全 / 2: 速い / 0: 最速 危険)
FAQ
Q: COMMIT 後にやっぱり巻き戻したい
A: 不可能です。新しい UPDATE/DELETE で論理的に元に戻すか、バックアップから戻すしかありません。
Q: COMMIT 中にDBクラッシュしたら?
A: WAL/REDO ログから再起動時にロールフォワード or ロールバックされます。これがACIDのD(永続性)を保証する仕組みです。
Q: AUTOCOMMIT は ON / OFF どちらが良い?
A: 対話的SQLは ON のままで便利。アプリは明示 OFF + コードで COMMIT 制御が推奨。