# VACUUM 与 ANALYZE

> 使用 VACUUM、ANALYZE 和自动清理维护行可见性与规划器统计信息。

---

LLMS 索引： [llms.txt](/llms.txt)

---

PostgreSQL 使用多版本并发控制（MVCC）：`UPDATE` 不会覆盖一行，而是写入一个新版本，并将旧版本标记为*死行*。`DELETE` 只会将行标记为死行——它们仍留在磁盘上。`VACUUM` 回收这部分空间，并防止事务 ID 回卷。`ANALYZE` 更新规划器的统计信息，使其能够做出合理的决策。PostgreSQL 通过*自动清理*自动运行这两个命令，但理解它们做什么，以及何时需要手动干预，是成为合格运维人员的一部分。

## 本章要点 {#learn}

- MVCC 为什么会产生死行，以及 VACUUM 如何处理它们
- `VACUUM`、`VACUUM FULL` 与 `VACUUM ANALYZE` 的区别
- `ANALYZE` 实际计算了什么
- 读取 `pg_stat_user_tables`，查看自动清理活动
- 事务 ID 回卷的临界点，以及为什么不能忽视它

## 示例 {#example}

```sql {num="53.1" caption="VACUUM 与 ANALYZE"}
-- Inspect the per-table autovacuum stats
SELECT relname, n_live_tup, n_dead_tup,
       last_vacuum, last_autovacuum,
       last_analyze, last_autoanalyze
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_dead_tup DESC
LIMIT 5;

-- Manually vacuum and analyze a table
VACUUM (VERBOSE) fruits;
ANALYZE VERBOSE fruits;

-- Combined: VACUUM ANALYZE (most common manual invocation)
VACUUM ANALYZE fruits;

-- See the planner's stored statistics for a column
SELECT attname, n_distinct, most_common_vals, most_common_freqs
FROM pg_stats
WHERE schemaname = 'public' AND tablename = 'fruits';

-- Bloat estimate (approximation): pg_stat_user_tables gives live vs dead row counts
-- pgstattuple extension gives exact numbers if you need them.

-- VACUUM FULL: rewrites the whole table, fully reclaims space, takes an exclusive lock.
-- Use sparingly.
-- VACUUM FULL fruits;
```

MVCC 让 PostgreSQL 能够实现高并发：由于每个事务都能看到一致的快照，写入者不会阻塞读取者，反之亦然。代价就是死行。`VACUUM` 遍历表，将死行占用的空间标记为可复用，并更新可见性映射（一种按页维护的位图，让仅索引扫描在标记为全可见的页面上无需检查堆可见性）。

普通 `VACUUM` 通常会让空间在表内变得可复用。它还可以截断表末尾的空页，并将这部分空间归还给操作系统。`VACUUM FULL` 会在新文件中重写整张表，释放旧文件——它能回收空间，但需要获取 `ACCESS EXCLUSIVE` 锁，而且速度很慢。很少使用它；日常维护由自动清理负责。

`ANALYZE` 对表进行抽样，以更新 `pg_statistic`。规划器使用这些统计信息——非重复值的数量、高频值和直方图——来估算行数并选择计划。过时的统计信息会产生糟糕的计划。

PostgreSQL 的*自动清理*守护进程会根据每张表的阈值（`autovacuum_vacuum_scale_factor`、`autovacuum_analyze_scale_factor`）自动运行 `VACUUM` 和 `ANALYZE`。自动清理负责日常维护，但仍需监控它的进度和阈值，尤其是在批量变更之后，或针对大型表和频繁更新的表。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/vacuum-and-analyze.sql postgres
 relname | n_live_tup | n_dead_tup | last_vacuum | ...
---------+------------+------------+-------------+----
 ...
INFO:  vacuuming "public.fruits"
INFO:  ...
VACUUM
 ...
```

## 常见陷阱 {#pitfalls}

- 为了“节省资源”而禁用自动清理——这几乎总是一个错误。死行会不断堆积，查询会变慢，最终追赶的代价远高于自动清理在稳定运行时的开销。
- 在繁忙的表上运行 `VACUUM FULL`——在整个运行期间，它会阻塞所有读写。使用 `pg_repack`（扩展）进行在线压实。
- 批量导入后忘记运行 `ANALYZE`——在自动清理追赶上来之前，规划器没有新行的统计信息，可能会选择非常糟糕的计划。大量导入后，手动运行 `ANALYZE schema.table;`。
- 长事务会阻止自动清理清除对其可见的行。在 psql 中遗留的 `BEGIN;` 可能导致整个数据库的清理都无法进行——要留意 `pg_stat_activity` 中 `state = 'idle in transaction'` 的状态。

> [!TIP] 提示
> `pg_stat_user_tables.n_dead_tup` 是判断“是否需要清理”的权威指标。在繁忙表中，`n_dead_tup / n_live_tup` 比例较高，说明自动清理跟不上——使用 `ALTER TABLE t SET (autovacuum_vacuum_scale_factor = 0.02);` 调整每张表的阈值。

> [!NOTE] 动手练习
> 在一张表上运行一批 `UPDATE`，然后查看 `pg_stat_user_tables` 中的 `n_dead_tup`。运行 `VACUUM` 并再次检查。然后在 `ANALYZE` 前后分别对同一个查询运行 `EXPLAIN ANALYZE`，看看估算值是否发生变化。

## 下载 {#downloads}

SQL 文件：[vacuum-and-analyze.sql](/examples/source/vacuum-and-analyze.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
