PostgreSQL / psql 调优
下载地址
https://www.zhengdianxue.com/wp-content/uploads/skills/org-e3rjs5bu--psql-sql-tuning.zip将提示词发送给你的 AI 安装该 skills
请根据 https://skillhub.cn/install/skillhub.md,安装 @org-e3rjs5bu/psql-sql-tuning。在编写、审查、解释或调优 PostgreSQL / psql SQL 时使用。当遇到以下情况时触发(Trigger):EXPLAIN ANALYZE、Seq Scan、Bitmap Heap Scan、Index Only Scan、VACUUM、ANALYZE、部分索引、表达式索引、pg_trgm、GIN、JSONB、CTE MATERIALIZED、OFFSET、NOT IN、DISTINCT、work_mem、pg_stat_statements、hashtext,或者 heap-table(堆表)索引问题。
不适用于 GaussDB(DWS) 的 DISTRIBUTE BY 语法。
阅读全文 (展开查看完整说明)
概述
PostgreSQL 是堆表(heap)单机库。性能首先由统计信息准不准、索引类型对不对、计划是 Seq Scan 还是 Index Scan决定。
语法像 GaussDB,执行不是 MPP。看到 DISTRIBUTE BY HASH、CN/DN、倾斜率,改用 gaussdb-sql-tuning。MySQL / Hive 分别用 mysql-sql-tuning、hsql-tuning。
本技能基线:PostgreSQL 14+(CTE 默认可内联;用 EXPLAIN ANALYZE)。psql 只是客户端。
何时使用
- 写/改 PostgreSQL 建表、索引、JOIN、分页、JSONB、模糊查询
EXPLAIN ANALYZE出现 Seq Scan、外部排序、行数估计差几个数量级- 表膨胀、统计信息过期、部分索引 / 表达式索引 / GIN 选型
不要用本技能处理:GaussDB 分布列、Hive 分区分桶、MySQL utf8mb3、装库运维。
调优收益顺序
统计信息与索引 > SQL 改写 > VACUUM / 锁 > 参数 / 硬件
计划拿着过期 pg_statistic 做选择。先 ANALYZE,再谈 work_mem。
写任何表之前
堆表不会按主键聚簇。主键仍要窄、单调,方便二级索引和分页,不要指望它决定物理顺序。
CREATE TABLE order_info (
order_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
cust_id BIGINT NOT NULL,
order_status SMALLINT NOT NULL DEFAULT 1,
amount NUMERIC(12,2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
title TEXT
);
CREATE INDEX idx_order_cust_created
ON order_info (cust_id, created_at DESC);| 场景 | 选择 |
|---|---|
| OLTP 主键 | BIGINT GENERATED BY DEFAULT AS IDENTITY |
| 外部 UUID / 业务号 | 唯一约束,不要当宽主键 |
| 状态 / 类型 | SMALLINT 或短英文码,禁止 '待发货' |
| 等值 + 排序 | 复合 B-Tree,等值列在前 |
| 只索引「未完成」订单 | 部分索引 WHERE status IN (1,2) |
| 必须对表达式过滤 | 表达式索引,不要指望普通列索引 |
'%词%' / JSONB 键查询 | pg_trgm GIN 或 JSONB GIN |
| 追加写入的时间序列大表 | 考虑 BRIN(created_at) |
索引:references/indexes.md 类型与函数:references/types.md
SQL 改写速查
| 不要 | 改成 |
|---|---|
SELECT * | 只要用到的列;争取 Index Only Scan |
WHERE created_at::date = | 范围谓词:>= ts AND < ts |
'%keyword%' 普通 B-Tree | pg_trgm 或右模糊 'keyword%' |
OFFSET 100000 | 键集分页 (created_at, order_id) |
NOT IN (SELECT ...) | NOT EXISTS |
SELECT DISTINCT | GROUP BY;给分组键索引 |
GROUP_CONCAT | STRING_AGG(col, ',' ORDER BY ...) |
过滤写在 HAVING | 行过滤放 WHERE;条件聚合用 FILTER |
| PG 11 习惯把 CTE 当优化器屏障 | PG 12+ 默认内联;要物化写 MATERIALIZED |
'abc' = 0 指望隐式转换 | 两边类型一致,否则直接报错 |
完整正反例:references/sql-patterns.md
诊断
默认 EXPLAIN (ANALYZE, BUFFERS),不要只看 EXPLAIN。
红旗:
Seq Scan扫大表rows估计与actual差一个数量级以上Sort Method: external merge(落盘排序)- 该走 Index Only Scan 却在 Heap Fetches 很高
- CTE 被
Materialize后仍被当小结果去 Nested Loop
步骤:references/diagnose.md
交付检查
写完 SQL / 索引后按顺序回答,缺一项就改:
EXPLAIN ANALYZE的估计行数是否离谱?有没有先ANALYZE?- 计划是否还是大表 Seq Scan?索引类型(B-Tree / GIN / 部分 / 表达式)是否匹配谓词?
- 索引列有没有被
::date/ 函数包住? - 分页是否还在用大
OFFSET? - 是否还在用
SELECT *、DISTINCT、NOT IN? - 这是不是 GaussDB?有没有误写
DISTRIBUTE BY?
常见借口
| 借口 | 事实 |
|---|---|
| 这就是 GaussDB | 语法像 PG。GaussDB 不写分布列会按第一列散列。单机 PG 没有 DN。 |
| 主键会把数据排好 | 堆表默认不聚簇。CLUSTER 是一次性的,随后又乱。 |
先调 work_mem / shared_buffers | 统计信息错了,内存只是让错误计划跑得更欢。 |
EXPLAIN 看起来用了索引 | 必须看 ANALYZE 的 actual rows 和 Buffers。 |
| CTE 一定只算一次 | PG 12+ 默认内联,可能算多次;要算一次写 MATERIALIZED。 |
| 中文状态更好读 | 与 MySQL 相同:硬编码、乱码、索引更胖。 |