MySQL | SQLite経験者向け、30日で習得するMySQL:実務SQL力 - Day14 トランザクション

SQL MySQL
スポンサーリンク

Day14 前半のゴール

「“お金の振込”をイメージしながらトランザクションを理解する」

今日のテーマはトランザクションとロックです。
ここから一気に「実務っぽさ」が増えます。

まず一言で言うと、トランザクションはこうです。

「この一連の処理は、全部成功するか、全部なかったことにするか」

Day14 前半のゴールは、

BEGIN / COMMIT / ROLLBACK の意味と流れを理解する
「途中で失敗しても“中途半端な状態”を残さない」イメージを持つ
ロックという言葉を“ざっくり”イメージできるようにしておく

ここまで行けば、後半の「ロックの種類」「競合の具体例」が入ってきます。


トランザクションとは何か

「“ひとかたまりの処理”を1単位として扱う仕組み」

まずはイメージからいきます。

テーブルは「データの箱」
SQLは「箱に対する操作」

トランザクションは、その操作をまとめる「枠」です。

例えば、銀行振込を考えます。

Aさんの口座から1万円引く
Bさんの口座に1万円足す

この2つはセットで成功してほしいですよね。

Aからは引かれたのに、Bに入っていない
Bに入ったのに、Aから引かれていない

こういう「中途半端な状態」は絶対にダメです。

トランザクションは、こういうときに使います。

「この2つ(あるいはもっと多く)のSQLは、ひとかたまりとして扱ってくれ。全部成功したら確定、途中で失敗したら全部なかったことにしてくれ」

これがトランザクションです。


BEGIN / COMMIT / ROLLBACK の基本

「“録画開始・保存・取り消し”の3ボタン」

MySQL では、トランザクションはだいたいこう書きます。

START TRANSACTION;  -- または BEGIN;

-- ここに複数のSQLを書く

COMMIT;   -- ここで確定
-- または
ROLLBACK; -- ここで全部なかったことにする
SQL

イメージとしては、

START TRANSACTION / BEGIN
「ここから先の変更を“まとめて扱う”モードに入る」

COMMIT
「ここまでの変更を正式に保存する」

ROLLBACK
「ここまでの変更を全部取り消して、BEGIN前の状態に戻す」

という3つのボタンです。

重要なのは、

BEGIN から COMMIT / ROLLBACK までの間に行われた変更は、
COMMIT するまでは“仮の状態”として扱われる

という点です。


具体例:銀行振込テーブルで考える

「2つのUPDATEを“ひとかたまり”にする」

シンプルな口座テーブルを考えます。

CREATE TABLE accounts (
  id      INT AUTO_INCREMENT PRIMARY KEY,
  name    VARCHAR(50) NOT NULL,
  balance INT         NOT NULL
) ENGINE=InnoDB;
SQL

Aさん(id=1)からBさん(id=2)に1万円振り込む処理は、本質的にはこうです。

UPDATE accounts
SET balance = balance - 10000
WHERE id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE id = 2;
SQL

これをトランザクションなしで実行すると、

1つ目のUPDATEは成功
2つ目のUPDATEでエラー(例:Bさんの行がロックされていた)

という状況になったとき、

Aさんの残高だけ減って、Bさんには入っていない

という「中途半端な状態」がDBに残ってしまいます。

これを避けるために、トランザクションを使います。


振込処理をトランザクションで書く

「途中で失敗したら全部 ROLLBACK」

同じ処理を、トランザクション付きで書くとこうなります。

START TRANSACTION;

UPDATE accounts
SET balance = balance - 10000
WHERE id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE id = 2;

COMMIT;
SQL

この場合、

2つのUPDATEが両方成功したら COMMIT され、
Aさんの残高減少とBさんの残高増加がセットで確定します。

もし2つ目のUPDATEでエラーが起きたら、
アプリ側やストアドプロシージャ側で ROLLBACK を呼び出すことで、

BEGIN 前の状態(振込前の残高)に戻す

ことができます。

ここでの本質は、

「成功したらまとめて反映、失敗したらまとめてなかったこと」

という一点です。


トランザクションがない世界との違い

「“1文ずつ即確定”から“まとめて確定”へ」

SQLite をデフォルト設定で使っていたとき、
多くの場合は「1つのSQLが終わるたびに自動で確定」していました。

MySQL でも、オートコミット(autocommit = 1)が有効だと、

1つのUPDATE / INSERT / DELETEが終わるたびに自動でCOMMIT

という動きになります。

トランザクションを明示的に使うというのは、

「自動で確定」モードを一時的に止めて、
自分で BEGIN / COMMIT / ROLLBACK を管理する

ということです。

これによって、

複数のSQLを「1つの論理的な操作」として扱える

ようになります。


ロックの概念を“ざっくり”つかむ

「“今この行はこの人が触ってるから、ちょっと待って”という仕組み」

トランザクションとセットで出てくるのが「ロック」です。

ロックを一言で言うと、

「このデータは今このトランザクションが触っているから、他の人は勝手に変えないで」

という“予約札”のようなものです。

例えば、

トランザクションAが、ある行の残高を更新しようとしている
その途中で、トランザクションBも同じ行を更新しようとする

このとき、何も仕組みがなければ、

AとBが同時に古い残高を読み、
それぞれ勝手に新しい残高を書き込んでしまう

という「書き込みの競合」が起きます。

ロックは、これを防ぐためにあります。

「Aがこの行を更新中だから、BはAが終わるまで待ってね」

というルールを、DBが自動で守ってくれます。


トランザクションとロックの関係

「“ひとかたまりの処理”の間、必要なデータを守る」

トランザクションを開始して、
ある行をUPDATEすると、その行にはロックがかかります。

そのトランザクションが COMMIT / ROLLBACK で終わるまで、
他のトランザクションはその行を勝手に更新できません。

これによって、

Aさんの残高を減らしている途中で、
別の処理が同じAさんの残高をいじってしまう

といった事故を防げます。

イメージとしては、

BEGIN で「作業開始」
必要な行にロックをかけて作業
COMMIT で「作業完了、ロック解除」

という流れです。

Day14 前半では、

ロック=「トランザクション中にデータを守るための“予約札”」

くらいのイメージを持っておけば十分です。


トランザクションが必要になる典型パターン

「“複数テーブル・複数行にまたがる更新”は基本トランザクション」

ここまでの話を、実務寄りにまとめると、

トランザクションを使うべきなのは、特にこんな場面です。

1つの操作が、複数のUPDATE / INSERT / DELETEから成り立っている
(振込、在庫引き当て+注文作成、など)

複数テーブルにまたがる整合性を守りたい
(注文テーブルと在庫テーブル、ユーザーと権限テーブル、など)

途中でエラーが起きたときに「中途半端な状態」を絶対に残したくない

逆に、

1行だけINSERTする
1行だけUPDATEする

といった単純な操作は、
オートコミットのままでも問題ないことが多いです。

Day14 の段階では、

「これは“ひとかたまり”として扱いたい処理か?」

と自分に問いかけて、
YES ならトランザクションを意識する、という癖をつけていくといいです。


Day14 前半のまとめ

トランザクションは「複数のSQLをひとかたまりの操作として扱い、全部成功したら COMMIT で確定し、途中で問題があれば ROLLBACK で BEGIN 前の状態に戻す」仕組みであり、銀行振込のように「Aから引いてBに足す」の2つのUPDATEをセットで成功させたい場面で、「片方だけ反映される」という中途半端な状態を防ぐために使う。
MySQL では START TRANSACTION(または BEGIN)でトランザクションを開始し、その後の UPDATE / INSERT / DELETE は COMMIT されるまで“仮の状態”として扱われ、同時に更新対象の行にはロックがかかるため、他のトランザクションが同じ行を勝手に書き換えることを防ぎ、「この行は今この処理が触っているから、終わるまで待って」という“予約札”のような役割を果たす。
SQLite の「1文ごと自動確定」的な世界から一歩進んで、「複数テーブル・複数行にまたがる更新を1つの論理操作として扱う」「途中で失敗したら全部なかったことにする」という感覚を持ち、特に振込・在庫引き当て・複数テーブル更新のような“絶対に中途半端にしたくない処理”では、トランザクションとロックをセットで意識することが、実務的なSQL力の土台になる。

後半では、
ロックの種類(行ロック・テーブルロック)や、
トランザクション同士がぶつかったときに何が起きるか(待ち・デッドロック)、
そして「安全にトランザクションを設計するコツ」を、
具体的な例と一緒に掘り下げていきます。

タイトルとURLをコピーしました