※この記事にはアフィリエイト広告(プロモーション)が含まれます。
PostgreSQL では、DELETE した行はすぐには消えません。行には「削除済み」の印が付くだけで、表のファイルの大きさはそのままです。この削除済みの行(dead tuple)を片付けるのが VACUUM です。
この記事のログは PostgreSQL 18.6(Ubuntu 26.04 の標準パッケージ)で採りました。表のサイズは pg_relation_size()、行数は pg_stat_user_tables で見ています。
まず見る(結論)
VACUUMは削除済みの行の場所を再利用できるようにするだけで、基本的にファイルは縮まない。縮むのは表の末尾がまとめて空いたときだけ- ファイルを縮めるのは
VACUUM FULL。表を作り直すので、実行中はSELECTも止まる VACUUMしても行が消えないときは、古いトランザクションが残っている。pg_stat_activityのbackend_xminを見る- 普段は autovacuum が自動で
VACUUMを流している。手で流すのは大量に削除した直後くらい
DELETE しても表の大きさは変わらない
20万行(約46MB)の表から18万行を DELETE しました。t_scatter は10行に9行を飛び飛びに、t_tail は id が 20001 以上の行(表の後ろ側)を消しています。
DELETE FROM t_scatter WHERE id % 10 <> 0; -- 180000行
DELETE FROM t_tail WHERE id > 20000; -- 180000行
SELECT relname, pg_size_pretty(pg_relation_size(relid)) AS heap, n_live_tup, n_dead_tup
FROM pg_stat_user_tables;
relname | heap | n_live_tup | n_dead_tup
-----------+-------+------------+------------
t_scatter | 46 MB | 20000 | 180000
t_tail | 46 MB | 20000 | 180000
どちらも 46MB のままです。n_dead_tup が削除済みの行の数です。
VACUUM で縮むのは末尾が空いた表だけ
両方の表に VACUUM (VERBOSE) を流しました。
VACUUM (VERBOSE) t_scatter;
INFO: finished vacuuming "vdb.public.t_scatter": index scans: 1
pages: 0 removed, 5883 remain, 5883 scanned (100.00% of total), 0 eagerly scanned
tuples: 180000 removed, 20000 remain, 0 are dead but not yet removable
VACUUM (VERBOSE) t_tail;
INFO: table "t_tail": truncated 5883 to 589 pages
INFO: finished vacuuming "vdb.public.t_tail": index scans: 1
pages: 5294 removed, 589 remain, 5883 scanned (100.00% of total), 0 eagerly scanned
tuples: 180000 removed, 20000 remain, 0 are dead but not yet removable
| 表 | DELETE 後 | VACUUM 後 |
|---|---|---|
t_scatter(飛び飛びに削除) |
46 MB | 46 MB |
t_tail(末尾を削除) |
46 MB | 4712 kB |
VACUUM がファイルを切り詰めるのは、表の末尾に空のページが続いているときだけです(truncated 5883 to 589 pages)。途中に生きている行が散らばっている t_scatter は、大きさが変わりませんでした。
VACUUM の後に15万行を入れても t_scatter は 46MB のままで、空いた場所が再利用されていました。
VACUUM しても行が消えないとき
別のセッションで REPEATABLE READ のトランザクションを開いたまま、行を削除して VACUUM しました。
VACUUM (VERBOSE) t_snap;
INFO: finished vacuuming "vdb.public.t_snap": index scans: 0
pages: 0 removed, 1471 remain, 1471 scanned (100.00% of total), 0 eagerly scanned
tuples: 0 removed, 50000 remain, 25000 are dead but not yet removable
25000 are dead but not yet removable は、削除済みだがまだ消せない行の数です。開いているトランザクションが削除前の行を見られる状態なので、VACUUM は消しません。原因のセッションは backend_xmin が入っている行で分かります。
SELECT pid, state, backend_xmin, now() - xact_start AS xact_age, left(query, 60) AS q
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL AND pid <> pg_backend_pid();
pid | state | backend_xmin | xact_age | q
------+--------+--------------+-----------------+-----------------------------------------------------
1952 | active | 771 | 00:00:02.084101 | begin isolation level repeatable read; select count(*) from
そのトランザクションが終わった後に同じ VACUUM を流すと、25000 removed になりました。xact_age が長いセッションや、state が idle in transaction のまま放置されたセッションがあると、データベース全体で削除済みの行が溜まり続けます。
VACUUM FULL で縮める
ファイルを縮めるには VACUUM FULL を使います。生きている行だけを新しいファイルに書き直すので、t_scatter は 46MB から 4712 kB になりました。
VACUUM (FULL, VERBOSE) t_scatter;
INFO: vacuuming "public.t_scatter"
INFO: "public.t_scatter": found 0 removable, 20000 nonremovable row versions in 5883 pages
DETAIL: 0 dead row versions cannot be removed yet.
VACUUM FULL は実行中ずっと表を排他ロックします。別のセッションが同じ表を SELECT している最中に、lock_timeout を2秒にして流した結果です。通常の VACUUM は通り、VACUUM FULL はロックを取れずに2秒で止まりました。
SET lock_timeout = '2s';
VACUUM t_scatter;
VACUUM
SET lock_timeout = '2s';
VACUUM FULL t_scatter;
ERROR: canceling statement due to lock timeout
大きな表で使うときは、止めてよい時間帯に流します。書き直す間は元のファイルと新しいファイルが両方あるので、生きている行のぶんの空きディスクも要ります。
autovacuum
autovacuum は既定で有効です。18.6 の既定値は次のとおりでした。
psql -Atc "SELECT name, setting, unit FROM pg_settings WHERE name IN ('autovacuum', 'autovacuum_naptime', ...) ORDER BY name"
autovacuum|on|
autovacuum_max_workers|3|
autovacuum_naptime|60|s
autovacuum_vacuum_insert_threshold|1000|
autovacuum_vacuum_scale_factor|0.2|
autovacuum_vacuum_threshold|50|
削除済みの行が「autovacuum_vacuum_threshold + 行数 × autovacuum_vacuum_scale_factor」を超えた表に、autovacuum_naptime(60秒)ごとに VACUUM がかかります。10万行の表で5万行を削除すると、1分以内に last_autovacuum に時刻が入り、n_dead_tup は 0 になりました。表のサイズは 23MB のままでした。autovacuum もファイルは縮めません。
n_dead_tup が大きいまま last_autovacuum が古い表があれば、前の節の長いトランザクションを疑います。
行を消してもディスクが空かないのは MySQL(InnoDB)も同じです。仕組みと戻し方の違いは「MySQLでDELETEしてもディスクが減らない」で扱っています。