この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: DELETE文
SEOタイトル: SQL DELETE 文 完全ガイド|WHERE 句必須、ROLLBACK、TRUNCATE との違い、Oracle/MySQL/PostgreSQL の違い

この記事の要点
  • DELETE 文はテーブルから行を削除する DML。DELETE FROM テーブル WHERE 条件 が基本形
  • WHERE 句を忘れると全行削除される。本番作業では必ずトランザクション内で SELECT による事前確認を行うこと
  • Oracle はデフォルトで自動コミットされず、COMMIT までは ROLLBACK で取り消し可能
  • 全行削除なら TRUNCATE の方が高速だが、DDL なので ROLLBACK できず権限・挙動も異なる

DELETE 文とは

DELETE 文は、SQL でテーブルから既存の行を削除するための DML (Data Manipulation Language) ステートメントです。Oracle、MySQL、PostgreSQL、SQL Server など主要な RDBMS で共通して使えます。

誤って実行すると業務データを失うリスクが大きいため、本記事では構文だけでなく安全に運用するための作法もまとめます。

基本構文

DELETE FROM テーブル名
WHERE 削除条件;

例として、users テーブルから id = 100 の行を削除する場合は次のように書きます。

DELETE FROM users
WHERE id = 100;

WHERE 句を忘れると全行削除される

DELETE 文で最も危険なのは、WHERE 句を忘れて全行を削除してしまうことです。次の SQL はテーブルのすべての行を消します。

-- 危険: users テーブルの全行が消える
DELETE FROM users;

本番環境では、DELETE を実行する前に必ず同じ WHERE 句で SELECT して件数を確認するのが鉄則です。

-- 1) 削除前: 件数確認
SELECT COUNT(*) FROM users WHERE last_login < '2020-01-01';

-- 2) 期待件数なら DELETE 実行
DELETE FROM users WHERE last_login < '2020-01-01';

-- 3) コミット
COMMIT;

トランザクションと ROLLBACK

Oracle や PostgreSQL では暗黙的にトランザクションが始まるため、COMMIT を実行するまで変更は確定しません。誤って削除した場合は ROLLBACK で元に戻せます。

-- Oracle / PostgreSQL のセッション
DELETE FROM orders WHERE order_id = 999;
-- まだコミットしていない

ROLLBACK;  -- 削除を取り消す

MySQL の InnoDB エンジンも同様にトランザクションをサポートしますが、デフォルト設定では autocommit=1 となっており、各文が即座にコミットされます。明示的にトランザクションを使う場合は START TRANSACTION で開始します。

-- MySQL
START TRANSACTION;
DELETE FROM orders WHERE order_id = 999;
ROLLBACK; -- まだ COMMIT していなければ取り消せる

JOIN を使った削除

関連テーブルを参照しながら削除したい場合は、サブクエリや JOIN を使います。書き方は DBMS によって少し異なります。

Oracle / PostgreSQL (サブクエリ)

-- 退会済みユーザーの注文をすべて削除
DELETE FROM orders
WHERE user_id IN (
    SELECT id FROM users WHERE status = 'withdrawn'
);

MySQL (マルチテーブル DELETE)

DELETE o
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'withdrawn';

RETURNING 句で削除内容を取得 (PostgreSQL)

PostgreSQL では、削除と同時に削除した行の内容を返す RETURNING 句が便利です。「念のため履歴を残す」「削除した数を確認する」用途に向きます。

DELETE FROM orders
WHERE order_date < '2020-01-01'
RETURNING id, user_id, total_price;

DELETE と TRUNCATE と DROP の違い

「テーブルを空にする」操作には DELETE 以外の選択肢もあります。用途に応じて選び分けましょう。

命令分類WHEREROLLBACK速度テーブル定義
DELETEDML遅い(行単位)残る
TRUNCATEDDL不可不可 (DBMS 依存)速い残る
DROP TABLEDDL不可不可 (DBMS 依存)速い消える

「全行削除して定義は残す」なら TRUNCATE が高速ですが、トリガが発火しない、ROLLBACK できない(DBMS による)、外部キー制約に阻まれる、など独自の制約があります。安全側に倒すなら DELETE + COMMIT が無難です。

外部キー制約と CASCADE

外部キー制約が貼られているテーブルから削除しようとすると、子テーブルに参照されている行は削除できずエラーになります。これを自動で連鎖削除させたい場合は、外部キー定義時に ON DELETE CASCADE を指定します。

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- users から削除すると orders 側も自動的に削除される
DELETE FROM users WHERE id = 100;

CASCADE は便利ですが、意図せず大量の子レコードを巻き込んで削除してしまう事故も起きやすいので、業務テーブルでは慎重に検討してください。

論理削除という選択肢

業務システムでは、物理的に DELETE するのではなく deleted_atdelete_flag といったカラムでマークだけする論理削除が広く採用されています。誤削除からの復旧、監査ログ、関連データの整合性維持などの観点で物理削除より柔軟です。

DELETE 文の内部動作とパフォーマンス

DELETE 文は単に行を消すだけでなく、各種ログ・インデックス・トリガを連動して処理します。InnoDB なら REDO ログと UNDO ログに変更が記録され、各インデックスエントリが論理的に削除済みとマークされます。実際の物理スペースは即座に解放されず、後続のパージ処理や OPTIMIZE TABLE で回収される設計です。そのため、数百万行を一気に削除する DELETE は非常に時間がかかり、ログ領域も圧迫するのが定石です。大量削除では数千〜数万行ずつ分割して COMMIT する、夜間帯に実行する、メンテナンスウィンドウで TRUNCATE を使う、といった工夫が必要になります。

LIMIT 付き DELETE で安全に分割

MySQL では DELETE FROM t WHERE 条件 LIMIT 1000 のように LIMIT を付けて、一度に削除する行数を制限できます。これをスクリプトでループさせれば、長時間ロックを取り続けることなく大量削除を進められます。PostgreSQL や Oracle は DELETE に LIMIT を直接付けられないため、サブクエリで主キーを 1000 件抽出してから WHERE IN で削除するパターンが定番です。本番運用では、削除進捗をログに残し、各バッチの間で短時間 sleep を挟むことで、レプリケーション遅延やバッファプール汚染も抑えられます。

削除時のロックとデッドロック

DELETE は対象行に排他ロック (X ロック) を取得し、コミットされるまで他のトランザクションからの更新を待たせます。複数のセッションが同じ行を異なる順序で削除/更新しようとすると、デッドロックが発生してどちらかがロールバックされます。デッドロックを減らすには、すべてのトランザクションで同じ順序で行をロックすることが基本です。たとえば「常に id の昇順で削除する」というルールを徹底すれば、循環待ちが発生しにくくなります。デッドロック発生時は DBMS が自動的に片方を犠牲にしてエラーにするため、アプリケーション側でリトライ処理を入れておくのが堅実です。

ORM での DELETE

Laravel Eloquent や Django ORM など、現代の Web フレームワークでは ORM 経由で DELETE することが多くなります。User::where('status', 'inactive')->delete() のような呼び出しは、内部で DELETE FROM users WHERE status = 'inactive' を発行します。ORM は論理削除(SoftDeletes)も標準でサポートしており、SoftDeletes trait を使うと deleted_at カラムへの UPDATE に置き換わり、通常クエリでは削除済み行が自動的に除外されます。物理削除したいときは forceDelete() を呼ぶ、削除済み行も含めて検索したいときは withTrashed() を呼ぶ、というインターフェイスが提供されています。

監査とリカバリ

業務システムでは「いつ・誰が・どのデータを削除したか」を残すことが法令や社内ポリシーで求められるケースが多くあります。トリガで監査テーブルに削除内容を記録する、あるいはアプリケーションログに DELETE 文と実行ユーザーを残す、CDC (Change Data Capture) で変更ストリームをイベントとして配信するといった選択肢があります。万一の誤削除に備えて、定期的なバックアップとポイントインタイムリカバリを設定しておくことが、運用上の最後の防波堤になります。

関連記事