实测 PostgreSQL 16.15 的行版本:用 pageinspect 看堆页,UPDATE 会插入新版本并填旧版本的 xmax,DELETE 只填 xmax,ROLLBACK 的版本留在页里只是标志无效;repeatable read 事务在别人删除后仍能看到旧行;只更新非索引列 1000 次全是 HOT 更新,表仍是 1 页。
实测 PostgreSQL 16.15 表膨胀与 VACUUM:20 万行全表 UPDATE 后表大小从 26.9 MB 翻倍到 53.9 MB;普通 VACUUM 清掉死行但文件不缩小,之后的更新重用空间;VACUUM FULL 缩回 26.9 MB 但会挡住 SELECT;只有尾部空页能被截断;老事务会让 VACUUM 回收不了。
实测 PostgreSQL 16.15 autovacuum 什么时候触发:门槛是 50 + 0.2 × 行数(1 万行的表 2050),更新 2000 行不触发、累计 2100 行触发;analyze 的门槛 1050 先到;10 万行的表默认要 20050,单表设 scale_factor = 0 后 1.0 s 内清理;老事务让 autovacuum 空转;日志里能看到 removed 行数。
实测 PostgreSQL 16.15 HOT 更新与 fillfactor:改有索引的列不可能是 HOT;只改没索引的列也要页里有空位;fillfactor 100/90/70/50 的 HOT 占比是 0%/12.2%/44.7%/100.0%;连续更新 5 轮 fillfactor 100 涨到 85.2 MB 而 50 不变;长事务会挡住剪枝(1 页变 5 页);fillfactor 50 的全表扫描 6.8 ms 对 4.5 ms。
实测 PostgreSQL 16.15 事务隔离级别:READ COMMITTED 同事务两次读不同、UPDATE 等锁后重新评估 WHERE;REPEATABLE READ 无幻读但更新别人改过的行报 could not serialize access due to concurrent update;SERIALIZABLE 拦下写偏斜;热点行 8 个会话重试 1180 次;先读后写丢 311 次更新,原子 UPDATE 和 FOR UPDATE 不丢。
实测 PostgreSQL 16.15 ALTER TABLE 为什么会卡住所有查询:长查询拿着 ACCESS SHARE,ALTER 排队,后来的普通 SELECT 被挡 1.59 s;pg_locks 里能看到排队的锁;lock_timeout 把 C 的等待降到 0.10 s;重试循环;各种 DDL 是否重写整表(add column 可空 0.6 ms,volatile 默认值 594.2 ms);CREATE INDEX CONCURRENTLY 不挡写入。
实测 PostgreSQL 16.15 索引为什么没被用:100 万行里取 1 行走 Index Scan、取 90% 走 Seq Scan 都是对的;30% 起索引扫描比全表扫描慢;对列做 lower()、加法、类型转换会让普通索引失效(89.28 ms 对 0.00 ms);LIKE 前缀在 en_US.UTF-8 下要 text_pattern_ops;random_page_cost = 1.1 让 50% 的查询换成更慢的 Index Scan(190.70 ms)。
实测 PostgreSQL 16.15 的索引类型:100 万行上 B-tree 18.8 MB、Hash 32.0 MB、BRIN 24 KB;BRIN 只在与物理顺序相关的列上有效(随机列 43.66 ms 等于没用,顺序列 0.44 ms);Hash 不支持范围;jsonb 的 GIN 与 jsonb_path_ops(12.0 MB 对 8.9 MB);pg_trgm 让 LIKE '%qzx%' 从 59.80 ms 降到 0.07 ms。
实测 PostgreSQL 16.15 怎么读 EXPLAIN ANALYZE:估计行数与实际行数对比;统计信息过期时估计 1 行、实际 900000 行,Nested Loop 767.68 ms,ANALYZE 后 109.37 ms;两列相关用 CREATE STATISTICS 把估计从 1030 修正到 10200;statistics target 对倾斜数据的影响;pg_stat_statements 找出平均 42.230 ms 的无索引查询。
实测 PostgreSQL 16.15 三种 JOIN 算法:小外表+索引用 Nested Loop(0.04 ms)、大批量用 Hash Join(144.82 ms 对 Nested Loop 550.43 ms);work_mem 4MB 时 Hash Join 分 8 批;Merge Join 在 ORDER BY 加 limit 时 47.30 ms 对 357.60 ms;外键列没索引从 0.23 ms 变 64.97 ms;NOT IN 放不进哈希时 10 秒超时,有 NULL 时一行都查不到。
实测 PostgreSQL 16.15 work_mem 与排序:100 万行 work_mem 4MB 时 Sort Method: external merge Disk: 17672kB,64MB 时 quicksort Memory: 55827kB;order by limit 10 用 top-N heapsort 只占 27kB;有索引时没有 Sort 节点 28.64 ms 对 258.66 ms;log_temp_files 的日志和 pg_stat_database;HashAggregate 4MB 时分 9 批写 23536kB。
实测 PostgreSQL 16.15 shared_buffers 与缓存命中:重启后第一次 read=3623、第二次全部 hit;全表扫描只占 33 个缓冲区不会灌满缓存;2 万次随机点查索引页命中率 95.4%、堆页 44.3%;shared_buffers 16MB/64MB/256MB 的 read 数 31857/13502/0;pg_prewarm 把 2745 + 15424 个页预热回缓冲区。