Q1DELETE と TRUNCATE の説明として正しいものはどれですか。
TRUNCATE — 全件削除の高速版とハイウォーターマーク
DELETE と TRUNCATE の違いを図で解説します。ハイウォーターマーク (HWM) のしくみ、実行後に取り消せるかどうかの違い、速度差の正体、Oracle / PostgreSQL / MySQL での使い分けまで押さえます。
本記事のコードは本番 RDBMS 用の例示
本記事で扱う TRUNCATE TABLE 構文は MySQL / PostgreSQL / Oracle / SQL Server などの本番 RDBMS で使う書き方で、本講座のブラウザ用コンソール(SQLite 採用)では TRUNCATE キーワードを直接は受け付けません。SQLite では DELETE FROM 表; が同じ効果を持ち、内部の truncate 最適化 によって TRUNCATE と同等の速度で動きます。
TRUNCATE — 全件を高速に削除する
テーブルの 全行をまとめて削除する 操作には、2 種類の書き方があります。1 つは 前の記事で扱った DELETE FROM 表;(WHERE なし)、もう 1 つが本記事で扱う `TRUNCATE TABLE 表;` です。どちらも結果としてテーブルが空になりますが、動き方と速度がまったく違います。
DELETE は 行を 1 行ずつ削除する DML(Data Manipulation Language = データ操作言語)命令で、削除した行ごとにログを残しながら処理します。一方 TRUNCATE は テーブル全体をリセットする DDL(Data Definition Language = データ定義言語)寄りの命令で、行ごとの記録をせず、テーブル定義そのものを「ほぼ作り直し」のように初期化します。
-- 1) DELETE で全件削除(行ごとに削除ログを残す、実行後の取り消しが可能)
DELETE FROM customer;
-- 2) TRUNCATE で全件削除(一括リセット、行ログは残さない、原則実行と同時に確定)
TRUNCATE TABLE customer;
-- ※ 本講座のコンソール(SQLite)では TRUNCATE キーワードは使わず、
-- DELETE FROM customer; が内部で truncate 最適化されて高速に動きます。
MySQL / PostgreSQL / Oracle での違い
TRUNCATE TABLE 表; の構文自体は MySQL / PostgreSQL / Oracle / SQL Server で共通ですが、細部の挙動は DB ごとに違います。
- MySQL: DDL 扱いで実行後の取り消し不可、AUTO_INCREMENT がリセットされる
- PostgreSQL: 実行後の取り消しが可能、RESTART IDENTITY で連番もリセットできる
- Oracle: DDL 扱いで実行後の取り消し不可、REUSE STORAGE / DROP STORAGE で領域の戻し方を選べる
- SQL Server: 実行後の取り消しが可能
ハイウォーターマーク — 速度差の正体
DELETE と TRUNCATE の速度差を理解する鍵が ハイウォーターマーク (High Water Mark, HWM) という概念です。HWM は テーブルがこれまでに使ったことのあるブロック (storage block) の最高位置 を指す目印で、Oracle が伝統的に使ってきた用語ですが、PostgreSQL / SQL Server / MySQL (InnoDB) でも 同等の最適化 が内部で働いています。
DB がフルスキャン(SELECT * FROM 表 のような全行走査)を行うときは、テーブルの先頭から HWM までの全ブロック を順番に読みます。HWM の位置が高いほど読むブロックが多くなり、空のブロックがあっても読み飛ばさないため、空のテーブルでも HWM が高ければスキャンに時間がかかります。
ここで重要なのが DELETE では HWM が下がらない という挙動です。DELETE FROM 表; で全行を削除しても、テーブルの中身は空になるだけで、ブロックそのものはセグメントに残ったまま。HWM の位置も block 5 のままです。次に SELECT COUNT(*) FROM 表; を流すと、行数は 0 ですが HWM 以下のブロックを最後まで全部読みに行く ため、削除前と同じくらいの時間がかかります。
TRUNCATE TABLE 表; は HWM をテーブル先頭に戻す 命令で、フルスキャンの対象範囲が一気に 0 ブロックになります。これが「TRUNCATE は大量行でも一瞬で終わる」と言われる正体です。
DELETE をしてもデータ量が減らない?
実務でデータ量を減らすために DELETE を流したのに、ストレージ使用量がほとんど減らない という事象に遭遇したことがあります。
原因はまさに HWM が下がらない ことでした。DELETE は行を論理的に消すだけで、テーブルが確保していたブロック(ページ)はそのまま残ります。ファイル上は領域を持ったままなので、OS から見たデータ量は変わりません。
領域そのものを縮めたいときは、DB ごとに用意された再構成コマンド(PostgreSQL の VACUUM FULL、MySQL (InnoDB) の OPTIMIZE TABLE、Oracle の ALTER TABLE ... SHRINK SPACE など)を実行するか、全件削除なら TRUNCATE を選びます。
DELETE と TRUNCATE の使い分け
実務では「全件消すなら TRUNCATE、条件付きなら DELETE」という単純な分け方ではなく、実行後の取り消しが必要か・トリガーの有無・処理速度の重要度 の 3 つで判断します。次の比較表が判断の基準になります。
| 項目 | DELETE | TRUNCATE |
|---|---|---|
| 削除単位 | 行ごと | テーブル全体 |
| WHERE 条件 | 使える | 使えない (常に全件) |
| 速度 (大量行) | 遅い (行ごとに処理) | 速い (一瞬) |
| 実行後の取り消し | 可能 | DB による (Oracle / MySQL は不可) |
| トリガー起動 | 起動する | 原則起動しない |
| AUTO_INCREMENT | 維持される | リセットされる (DB による) |
| ハイウォーターマーク | 動かない | リセットされる |
| 削除ログ | 行ごとに記録 (大量) | 最小限 |
| 分類 | DML | DDL (DB による) |
TRUNCATE の落とし穴
TRUNCATE は速くて便利ですが、取り返しがつかない操作 になりがちです。次の点に注意します。
- 実行した時点で確定する DB がある(Oracle / MySQL)。本番では事前に SELECT COUNT(*) で件数を確認してから打つ
- AUTO_INCREMENT がリセットされる(DB による)。ID を外部システムや URL で公開している場合、リセット後の採番衝突に注意
- 外部キー制約があると実行できないことがある(多くの DB)。subscription から customer への外部キーがあると、customer を TRUNCATE するには子テーブル側を先に空にする必要がある
- トリガーが原則起動しない。トリガー(trigger)は行の追加・更新・削除をきっかけに DB が自動で実行する SQL のしくみで、たとえば「行が削除されたら別テーブルに監査ログを書く」といった処理を仕込めます。DELETE ではこのトリガーが行ごとに実行されますが、TRUNCATE は行単位で動かないため、監査ログなどの記録が残らない
速度を最優先したい大量データの一括削除で TRUNCATE を選び、それ以外は DELETE WHERE や 実行後に取り消せる DELETE を選ぶのが安全です。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2ハイウォーターマーク (HWM) の説明として最も正しいものはどれですか。
Q3100 万行のテーブルを DELETE FROM 表;(WHERE なし)で空にした直後、SELECT COUNT(*) FROM 表; を流したときの動作として正しいものはどれですか。