第2部 SQLを使いこなす
第5章 SQL入門③ — 書き込みとトランザクション
読むだけのSQLから、書き換えるSQLへ。INSERT・UPDATE・DELETEは便利な反面、一撃で全データを壊せる力も持っています。事故を防ぐ実務の作法と、DBMS最大の武器「トランザクション」を学びます。
🎯 この章で学ぶこと
- INSERTの安全な書き方 — 列名指定・複数行INSERT・INSERT ... SELECT
- UPDATE・DELETEで最も怖い「WHERE忘れ」と、実行前SELECTという鉄則
- トランザクション(BEGIN / COMMIT / ROLLBACK)で「途中半端」をなくす
- ACID特性 — DBMSを使う最大の理由を4つの性質で説明できるようになる
- 複数人が同時に書き込むとき何が起きるか — ロックとデッドロックの初歩
この章も第3章で作った company.db(departments・employeesテーブル)を使います。第3章・第4章では SELECT でデータを「読む」ことだけをしてきました。読むだけなら何回失敗してもデータは無事です。しかしこの章から扱う書き込み系のSQLは、間違えるとデータそのものが変わってしまいます。だからこそ、文法と同じ重さで「事故を防ぐ手順」を学びます。
5.1 INSERT — データを追加する
行を追加する基本形は第1章でも登場した INSERT INTO テーブル VALUES (...) ですが、実務では列名を明示する書き方を推奨します。
-- 推奨: どの列に何を入れるかが読めば分かる
INSERT INTO employees (id, name, dept_id, hire_date, salary)
VALUES (7, '中村 大輔', 30, '2025-04-01', 310000);
-- 非推奨: 列の並び順に依存する(列が増えた瞬間に壊れる)
INSERT INTO employees VALUES (7, '中村 大輔', 30, '2025-04-01', 310000);
なぜ列名指定が良いのでしょうか。列名を省略した書き方は「テーブル定義の列の並び順」に暗黙に依存します。将来テーブルに列が追加されると、古いINSERT文は値の個数が合わずエラーになるか、最悪の場合ずれた列に値が入ります。列名を書いておけば、列が増えても既存のINSERT文はそのまま動きます。引っ越しの荷物に「食器」「本」とラベルを貼るのと同じで、ひと手間が後の事故を防ぎます。
複数行をまとめて入れる
VALUESをカンマで並べれば、1文で複数行を追加できます。1行ずつ7回実行するより速く、後述するトランザクションの観点でも「7件で1セット」として扱いやすくなります。
INSERT INTO employees (id, name, dept_id, hire_date, salary) VALUES
(8, '小林 恵', 10, '2025-04-01', 295000),
(9, '加藤 翔太', 20, '2025-04-01', 295000);
INSERT ... SELECT — 検索結果をそのまま入れる
VALUESの代わりにSELECT文を書くと、別のテーブルから検索した結果をまるごと追加できます。年度末に社員テーブルのスナップショットを保存する、といった場面の定番です。
-- 2025年度時点の社員一覧をアーカイブ用テーブルへコピー
CREATE TABLE employees_2025 AS SELECT * FROM employees WHERE 0; -- 器だけ複製
INSERT INTO employees_2025 (id, name, dept_id, hire_date, salary)
SELECT id, name, dept_id, hire_date, salary
FROM employees
WHERE hire_date <= '2025-03-31';
第3章で設定した NOT NULL や PRIMARY KEY は、INSERTの瞬間に検査されます。たとえば name を省略したINSERTは NOT NULL constraint failed というエラーで拒否されます。エラーは嫌なものに見えますが、「不正なデータが入ってしまってから直す」よりはるかに安上がりです。エラーを出してくれる設計こそ良い設計です。
5.2 UPDATEとDELETE — 「WHERE忘れ」の恐怖
既存の行を書き換えるのが UPDATE、行を削除するのが DELETE です。文法はシンプルです。
-- 田中さん(id=3)の給与を変更する
UPDATE employees SET salary = 300000 WHERE id = 3;
-- id=9 の行を削除する
DELETE FROM employees WHERE id = 9;
ここで、データベース史上おそらく最も多く繰り返されてきた事故を実演します。WHERE句を書き忘れたUPDATEです。
sqlite> UPDATE employees SET salary = 300000; -- WHEREを忘れた!
sqlite> SELECT name, salary FROM employees;
佐藤 花子|300000
鈴木 一郎|300000
田中 実|300000
高橋 美咲|300000
伊藤 健|300000
渡辺 由紀|300000
全社員の給与が30万円になりました。UPDATEとDELETEは「WHEREに合う行」ではなく、WHEREがなければ黙って全行を対象にします。DBMSは「本当に全行でいいですか?」とは聞いてくれません。SQLは命令された通りに、高速に、正確に、全行を書き換えます。速くて正確であることが、ここでは牙をむくのです。
実務の鉄則 — 実行前に同じWHEREでSELECTする
この事故への最も効果的な予防策は、拍子抜けするほど単純です。UPDATE/DELETEを打つ前に、同じWHERE句でSELECTして、対象の行と件数を目で確認する——これだけです。
sqlite> -- ①まずSELECTで「これから変える行」を確認
sqlite> SELECT id, name, salary FROM employees WHERE dept_id = 20;
2|鈴木 一郎|350000
5|伊藤 健|380000
sqlite> -- 2件。想定どおり。②WHERE句をそのまま流用してUPDATE
sqlite> UPDATE employees SET salary = salary + 10000 WHERE dept_id = 20;
sqlite> -- ③もう一度SELECTして結果を確認
sqlite> SELECT id, name, salary FROM employees WHERE dept_id = 20;
2|鈴木 一郎|360000
5|伊藤 健|390000
確認SELECTの結果が「想定100件のはずが3万件」だったら、WHERE条件が間違っています。実行してから気づくのと、実行する前に気づくのとでは、天国と地獄の差です。ベテランほどこの手順を省きません。むしろベテランは過去に一度やらかしたからこそ省かないのです。
5.3 トランザクション — 「両方成功」か「両方なかったこと」か
銀行振込を考えてみましょう。AさんからBさんへ1万円振り込む処理は、SQLでは2つのUPDATEになります。
UPDATE accounts SET balance = balance - 10000 WHERE name = 'A'; -- 出金
UPDATE accounts SET balance = balance + 10000 WHERE name = 'B'; -- 入金
もし1つ目が成功した直後にサーバーが停電したら?Aさんの1万円が消えて、Bさんには届いていない——世界から1万円が蒸発します。逆順なら1万円が湧いて出ます。つまりこの2文は、「両方成功」か「両方なかったこと」の二択でなければならず、「片方だけ成功」という中間状態は絶対に許されません。
この「複数の操作を1つの不可分なかたまりとして扱う仕組み」がトランザクションです。BEGIN で始め、確定するなら COMMIT、なかったことにするなら ROLLBACK を宣言します。
ROLLBACKで「なかったこと」にしてみる
company.dbで実際に見てみましょう。UPDATEした結果が、ROLLBACKで元に戻ります。
sqlite> SELECT name, salary FROM employees WHERE id = 1;
佐藤 花子|320000
sqlite> BEGIN;
sqlite> UPDATE employees SET salary = 999999 WHERE id = 1;
sqlite> SELECT name, salary FROM employees WHERE id = 1;
佐藤 花子|999999
sqlite> ROLLBACK;
sqlite> SELECT name, salary FROM employees WHERE id = 1;
佐藤 花子|320000
BEGINの後のUPDATEは「仮の書き込み」です。トランザクションの中では変更後の値が見えていますが、ROLLBACKした瞬間にすべて巻き戻りました。逆に COMMIT; を打てば、その時点で変更が正式に確定します。エラーが出たら途中でROLLBACK——ワープロの「保存せずに閉じる」に似ていますが、複数の変更をまとめて確定/破棄できる点が強力です。
sqlite3を含む多くの環境では、BEGINを打たない限り1文ごとに自動でCOMMITされます(オートコミット)。5.2節のWHERE忘れUPDATEが即座に「確定」してしまったのはこのためです。逆に言えば、先にBEGINさえ打っておけば、WHERE忘れに気づいてもROLLBACKで生還できます。
5.4 ACID特性 — DBMSを使う最大の理由
トランザクションが満たすべき4つの性質を、頭文字を取ってACID特性と呼びます。第1章で「ExcelではなくDBMSを使う理由」を4つの壁で説明しましたが、その技術的な裏付けがこれです。
| 性質 | 意味 | 身近な言い換え |
|---|---|---|
| 原子性(Atomicity) | トランザクション内の操作は「全部成功」か「全部取り消し」のどちらかにしかならない | 振込は「出金と入金セット」。片方だけは起きない |
| 一貫性(Consistency) | トランザクションの前後で、制約(NOT NULL・外部キーなど)が守られた正しい状態が保たれる | 「残高がマイナス不可」ならそれを破る変更は確定できない |
| 独立性(Isolation) | 同時に走る複数のトランザクションが互いの途中経過に干渉しない | 他人の「仮の書き込み」は見えない。会議室で個別に作業する感覚 |
| 耐久性(Durability) | COMMITした変更は、直後に電源が落ちても失われない | 「振込完了」と表示されたら、停電しても振込は完了している |
自前のプログラムでこの4つを実現するのは、実は途方もなく難しい仕事です。停電・クラッシュ・同時アクセスのあらゆる組み合わせを考慮しなければならないからです。その難しい部分を丸ごと引き受けてくれることこそ、DBMSを使う最大の理由です。「ファイルに自分で書けばいいのでは?」への最終回答がACIDだと言えます。
5.5 同時実行とロックの初歩
独立性(I)をもう少しだけ覗いてみます。AさんとBさんが同じ行を同時に更新したらどうなるでしょうか。たとえば在庫数10の商品を、2人が同時に「在庫を読んで、1減らして、書き戻す」処理をしたとします。素朴に実装すると、2人とも「10」を読み、2人とも「9」を書き戻し——2個売れたのに在庫は9、という矛盾が起きます。
DBMSはこれをロック(鍵)で防ぎます。先に更新を始めたトランザクションがその行(またはテーブル)に鍵をかけ、後から来たトランザクションは鍵が外れるまで待たされます。トイレの個室と同じで、使用中なら待つ——地味ですが、これが矛盾を防ぐ基本原理です。
ただし待ち方がこじれると、「AはBの鍵待ち、BはAの鍵待ち」でお互いに永遠に待ち続ける状態が起きえます。これをデッドロックと呼びます。DBMSは検出して片方を強制ROLLBACKしてくれますが、いまは「同時書き込みはロックで守られている」「デッドロックという詰まり方がある」という言葉を知っておけば十分です。トランザクションは必要な範囲で短く——これが同時実行の世界での礼儀です。
本番データベースでのUPDATE/DELETEは、攻撃されなくてもデータを失う最大のリスク要因です。実務では次の3点セットを手順として固定してください。①バックアップが取れていることを確認してから始める(戻せない作業を始めない)、②必ずBEGINでトランザクションを開いてから実行する、③変更件数と内容をSELECTで確認し、想定どおりのときだけCOMMITする(違ったらROLLBACK)。オートコミットのまま本番で作業するのは、命綱なしで屋根に登るのと同じです。誤操作対策とは根性や注意力ではなく、こうした「間違えても戻れる手順」を仕組みにすることです。可用性と完全性(CIAのAとI)は、攻撃対策だけでなく自分自身の操作ミスからも守るものだと覚えておいてください。
まとめ
- INSERTは列名を明示して書く。複数行INSERTやINSERT ... SELECTで、まとまった追加も1文で書ける
- UPDATE/DELETEはWHEREがなければ全行が対象。実行前に同じWHEREでSELECTして件数を確認するのが実務の鉄則
- トランザクション(BEGIN/COMMIT/ROLLBACK)は複数の操作を「全部成功か、全部なかったことか」の不可分な単位にする
- ACID特性(原子性・一貫性・独立性・耐久性)はDBMSを使う最大の理由。停電しても同時アクセスでも矛盾しない
- 同時更新はロックで守られる。お互いに待ち合うデッドロックという現象があり、トランザクションは短く保つのが礼儀
- 本番の書き込みは「①バックアップ確認 ②トランザクション内で実行 ③件数確認してCOMMIT」の3点セットで
練習問題
総務部(dept_id = 10)の社員全員の給与を5,000円昇給させてください。ただし実務の鉄則に従い、①実行前に対象行をSELECTで確認 → ②UPDATE → ③結果をSELECTで確認の3ステップで行うこと。それぞれのSQLと実行結果を示してください。
解答を見る
WHERE句を3ステップで使い回すのがポイントです。
sqlite> -- ①対象確認: 総務部は2名のはず
sqlite> SELECT id, name, salary FROM employees WHERE dept_id = 10;
1|佐藤 花子|320000
3|田中 実|280000
sqlite> -- ②同じWHEREでUPDATE
sqlite> UPDATE employees SET salary = salary + 5000 WHERE dept_id = 10;
sqlite> -- ③結果確認
sqlite> SELECT id, name, salary FROM employees WHERE dept_id = 10;
1|佐藤 花子|325000
3|田中 実|285000
①で「2件」と確認できているので、仮に②の後で件数や値がおかしければ、条件ミスにすぐ気づけます。SET salary = salary + 5000 のように現在の値を使った計算ができる点も確認しておきましょう。
トランザクションを使って、次の操作を実際に試してください。(1) BEGINを打つ、(2) employeesから渡辺さん(id = 6)をDELETEする、(3) SELECTで消えたことを確認する、(4) ROLLBACKする、(5) SELECTで渡辺さんが戻っていることを確認する。
解答を見る
sqlite> BEGIN;
sqlite> DELETE FROM employees WHERE id = 6;
sqlite> SELECT id, name FROM employees WHERE id = 6;
sqlite> -- 何も表示されない=消えている(ただしまだ「仮」)
sqlite> ROLLBACK;
sqlite> SELECT id, name FROM employees WHERE id = 6;
6|渡辺 由紀
ROLLBACKでDELETEが「なかったこと」になりました。逆に(4)でCOMMITを打っていたら削除は確定し、もう戻せません。「BEGINしてから作業すれば、COMMITするまで引き返せる」という感覚を、安全な学習用DBのうちに体で覚えておくことが、本番での命綱になります。
5.3節の銀行振込の例で、もしトランザクションを使わずに2つのUPDATEを実行し、1文目と2文目の間でプログラムが異常終了したら、データベースはどんな状態になりますか。また、トランザクションを使っていた場合はどうなりますか。ACIDのどの性質が効いているかも答えてください。
解答を見る
トランザクションなし: 出金のUPDATEだけがオートコミットで確定済みのため、Aさんの残高だけが1万円減り、Bさんには入金されていない中途半端な状態が残ります。合計金額が合わない、発見も復旧も難しい障害になります。
トランザクションあり: COMMIT前に異常終了した変更は確定していないので、再起動後にDBMSが未確定の変更を自動的に巻き戻し、振込全体が「なかったこと」になります(Aさんの残高も元のまま)。これは原子性(Atomicity)——「全部成功か、全部取り消しか」——が働いた結果です。また、確定済みの過去の振込が失われない点は耐久性(Durability)の保証です。