\l列出整个集群上所有数据库,带 owner、编码、collation、权限。psql 进去两眼一抹黑时第一条敲的命令。
⚠ 常见坑: 加 + 才能看到库体积(\l+)。库特别多时算 size 要扫一遍,可能卡住连接,卡了就退回 \l。
\l
\l+
\l app_* -- pattern match
PostgreSQL 速查表,80+ 命令和函数,psql、JSONB、CTE、窗口函数、索引、分区、高级扩展。
\l列出整个集群上所有数据库,带 owner、编码、collation、权限。psql 进去两眼一抹黑时第一条敲的命令。
⚠ 常见坑: 加 + 才能看到库体积(\l+)。库特别多时算 size 要扫一遍,可能卡住连接,卡了就退回 \l。
\l
\l+
\l app_* -- pattern match
\c dbname [user]在同一个 psql 会话里切到另一个库(可选另一个用户),不用退出重开。
⚠ 常见坑: 切库会丢会话状态 —— 临时表、prepared statement、search_path 配置都跟旧连接一起没了。
\c production
\c app_db readonly_user
\c - postgres -- same db, new user
\dt [pattern]列当前库所有表,可加 pattern 过滤。\dt *.* 包含系统表,\dt+ 多出体积和描述。
\dt
\dt+
\dt public.user*
\dt myschema.*
\d table描述一张表:列、类型、默认值、索引、外键、触发器、check 约束、继承关系。psql 里用得最多的一条。
⚠ 常见坑: \d 不带参数会列所有 relation 包括序列和视图 —— 通常不是你想要的。要看表用 \dt。
\d users
\d+ users -- with storage details
\d users_id_seq -- describe a sequence
\du列所有角色(用户 + 组)和属性:是不是 superuser、能不能建库 / 建角色 / 登录 / 复制、密码是否设置、过期时间。
\du
\du+ -- include description
\du app_*
\q退出 psql。等价于 exit、空行 Ctrl-D 或 \quit。
\q
\i file.sql执行一个 SQL 文件,路径是 psql 客户端机器上的路径,不是服务器上的。
⚠ 常见坑: \i 默认遇错不停(除非 SET ON_ERROR_STOP off)。生产脚本一定要 \set ON_ERROR_STOP on 并包 BEGIN / COMMIT。
\i schema.sql
\ir relative/migration.sql -- relative to current file
\set ON_ERROR_STOP on \i deploy.sql
\timing on开关每条 SQL 的执行耗时显示。"这条快不快?"先开 \timing 比直接 EXPLAIN 来得快。
\timing on
\timing off
\timing -- toggle
\x [on|off|auto]展开显示:宽行的每个列换行单独打印。\x auto 按终端宽度自动切换,窄行保持表格、宽行自动展开。
\x
\x on
\x auto
\! cmd在 psql 里跑 shell 命令,不用退出。\! 单独使用会开一个临时 subshell。
\! ls -la
\! pwd
\! -- enter subshell, type exit to return
\conninfo打印当前连接信息:用户、库、主机、端口、SSL 模式。跑任何破坏性操作前先 \conninfo 确认连对了集群。
\conninfo
\copy table FROM file通过客户端批量导入 / 导出 CSV。和服务器端 COPY 不同,\copy 不需要 superuser,读的是 psql 所在机器的文件。
⚠ 常见坑: 服务器端 COPY 更快(不用每行网络往返),但要 superuser 且文件必须在 DB 服务器上。从本机操作就用 \copy。
\copy users FROM 'users.csv' WITH CSV HEADER
\copy (SELECT * FROM orders WHERE status='paid') TO 'paid.csv' WITH CSV HEADER
\dn / \df\dn 列 schema(namespace)。\df 列用户定义函数(加 + 看源码,加 pattern 过滤)。\df S 包含系统函数。
\dn
\df
\df+ my_func
\df *json*
text / varchar / char三种字符串类型。99% 情况只用 text 就行 —— 无长度限制,存储和性能和 varchar 完全一样。varchar(n) 只多一个长度检查;char(n) 会补空格(基本永远用不到)。
⚠ 常见坑: varchar(20) 改 varchar(40) 只动元数据瞬时完成。varchar(20) 改 varchar(15) 要全表扫。直接用 text,长度约束放在应用层或者 CHECK。
CREATE TABLE users (id bigserial, name text, email text);
ALTER TABLE users ALTER COLUMN bio TYPE text;
int / bigint / smallint定长整数:smallint 2 字节(-3.2 万 ~ 3.2 万),int 4 字节(±21 亿),bigint 8 字节(±920 亿亿)。普通数字用 int,可能长大的表的 ID 列用 bigint。
⚠ 常见坑: ID 列用 int 是头号定时炸弹 —— 21 亿行后写入直接挂。所有主键 day 1 就用 bigint(或 bigserial / bigint generated as identity)。
age smallint
user_id bigint
CREATE TABLE orders (id bigserial PRIMARY KEY);
serial / bigserial / identity自增整数。serial = int + 序列 + 默认值。bigserial 是 bigint 版本。PG 10+ 增加了 SQL 标准写法 GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY,更推荐用它。
⚠ 常见坑: 序列不在事务里。回滚的 INSERT 也消耗 ID,所以 serial 永远不是连续的。要真正连续的单号(比如发票)得另写一套生成器。
id bigserial PRIMARY KEY
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY -- SQL standard
uuid128 位通用唯一 ID,磁盘上 16 字节。PG 13+ 内置 gen_random_uuid(),老版本装 uuid-ossp 扩展。分布式系统客户端生成 ID 时首选。
⚠ 常见坑: UUID 当主键伤 B 树缓存局部性(随机插入序导致页分裂)。UUID v7(按时间有序)能解决,PG 18 才内置;现在自己手动前缀加时间戳。
id uuid DEFAULT gen_random_uuid() PRIMARY KEY
SELECT gen_random_uuid();
timestamp / timestamptztimestamp(即 timestamp without time zone)存裸日期时间,不带时区不做转换。timestamptz 内部存 UTC,I/O 时按会话时区转换。一律用 timestamptz。
⚠ 常见坑: 存 timestamp 然后在应用层算时差是经典时区 bug。一律用 timestamptz;时区只在显示边界 SET TIME ZONE。
created_at timestamptz NOT NULL DEFAULT now()
SELECT created_at AT TIME ZONE 'Asia/Shanghai' FROM orders;
interval时间区间(月、日、微秒)。可以直接和 timestamp 算:now() + interval '7 days',ts1 - ts2 出 interval。
⚠ 常见坑: interval 把月和日分开存,因为它们长度不固定(2 月 28/29 天)。计费别用 interval '1 month',改成显式天数算。
SELECT now() + interval '1 hour';
SELECT now() - interval '7 days';
DELETE FROM sessions WHERE created_at < now() - interval '30 days';
jsonb / json都存 JSON。jsonb 是二进制、可索引、键去重、键排序后的形式,99% 用它。json 保留原始输入字节和空白(审计日志要保留原文时才用)。
⚠ 常见坑: jsonb 不保留键顺序和空白。json 不支持大部分操作符(没有 @>、不能建 GIN 索引)。默认 jsonb,除非有特定理由用 json。
config jsonb NOT NULL DEFAULT '{}'INSERT INTO events (payload) VALUES ('{"type":"click","x":42}'::jsonb);array (text[], int[])PG 原生支持任意类型的数组。小规模、有序、基本只追加的集合用它(标签、角色列表、有序偏好),避免单建 join 表。
⚠ 常见坑: 频繁查询的多对多关系不要用 array 替代关联表。array 内元素不能加外键;更新会重写整个数组;GIN 索引能查包含关系但不能查顺序。
tags text[] DEFAULT '{}'SELECT * FROM posts WHERE tags @> ARRAY['rust'];
UPDATE posts SET tags = array_append(tags, 'wasm') WHERE id = 1;
enum / CREATE TYPE固定枚举值。比 text+CHECK 更省空间也更自文档化,带原生排序。CREATE TYPE status AS ENUM (...)。
⚠ 常见坑: 删除枚举值必须重建整个类型 —— 设计时要慎重。ALTER TYPE ... ADD VALUE 是单向的。
CREATE TYPE order_status AS ENUM ('pending','paid','shipped','refunded');ALTER TYPE order_status ADD VALUE 'cancelled' AFTER 'pending';
CREATE TABLE建新表。列 = 类型 + 可选默认值 + 约束(NOT NULL、UNIQUE、PRIMARY KEY、REFERENCES、CHECK)。永远声明主键 —— PG 不强制但不写迟早后悔。
CREATE TABLE users ( id bigserial PRIMARY KEY, email text UNIQUE NOT NULL, created_at timestamptz NOT NULL DEFAULT now() );
CREATE TABLE IF NOT EXISTS幂等建表 —— 表已存在时静默成功。可能重跑的迁移脚本标配。
⚠ 常见坑: 只检查表存在,不查 schema。如果列变了,你拿到的是旧表,没告警。生产环境用真正的迁移工具。
CREATE TABLE IF NOT EXISTS audit_log (id bigserial PRIMARY KEY, action text);
ALTER TABLE改表:ADD / DROP / ALTER COLUMN、ADD / DROP CONSTRAINT、RENAME。每个子操作各取一次锁;多个子操作合在一条语句里只取一次锁。
⚠ 常见坑: ALTER TABLE ... ADD COLUMN col text DEFAULT 'x' 在 PG ≤10 会重写整张表。PG 11+ 非 volatile 默认值是元数据操作瞬时完成。volatile 默认值(比如 gen_random_uuid())仍然要重写。
ALTER TABLE users ADD COLUMN avatar_url text;
ALTER TABLE users DROP COLUMN deprecated_field;
ALTER TABLE users RENAME COLUMN nickname TO display_name;
ALTER TABLE users ADD CONSTRAINT users_email_lower CHECK (email = lower(email)) NOT VALID;
DROP TABLE [IF EXISTS] [CASCADE]删表。IF EXISTS 可重跑安全。CASCADE 同时删依赖对象(视图、指向此表的外键)—— 不加 CASCADE 有依赖时 PG 拒绝。
⚠ 常见坑: CASCADE 不可逆,静默删掉所有依赖的视图、函数、外键。先用默认的 DROP ... RESTRICT 看看会破坏什么,再决定要不要核弹。
DROP TABLE IF EXISTS old_users;
DROP TABLE users CASCADE; -- careful
CREATE INDEX [CONCURRENTLY]建索引。普通 CREATE INDEX 持 ACCESS EXCLUSIVE 锁 —— 整段时间堵写(老版本也堵读)。CONCURRENTLY 不堵写,耗时 2-3 倍但生产环境的唯一安全选项。
⚠ 常见坑: CREATE INDEX CONCURRENTLY 可能静默失败,留下 INVALID 索引。建完一定查:SELECT * FROM pg_indexes WHERE indexdef LIKE '%INVALID%'。失败就 DROP 重建。
CREATE INDEX CONCURRENTLY users_email_idx ON users (email);
CREATE UNIQUE INDEX CONCURRENTLY orders_uuid_uidx ON orders (uuid);
CREATE VIEW / MATERIALIZED VIEW视图 = 保存的查询,每次 SELECT 重跑。物化视图 = 查询结果落盘,手动 REFRESH MATERIALIZED VIEW 刷新。昂贵且可接受过期的聚合用物化视图。
⚠ 常见坑: REFRESH MATERIALIZED VIEW 会锁住读。要不堵读用 REFRESH MATERIALIZED VIEW CONCURRENTLY(视图上要有 UNIQUE 索引)。
CREATE VIEW active_users AS SELECT * FROM users WHERE deleted_at IS NULL;
CREATE MATERIALIZED VIEW daily_revenue AS
SELECT date_trunc('day', created_at) d, sum(amount) FROM orders GROUP BY 1;
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;GENERATED column从其他列算出来的列。STORED 落盘;VIRTUAL PG 还不支持(用视图代替)。
ALTER TABLE products ADD COLUMN search_text text GENERATED ALWAYS AS (lower(name) || ' ' || lower(coalesce(brand, ''))) STORED;
INSERT INTO ... VALUES插入一行或多行。批量 INSERT 比一行一条语句快得多(一次网络往返,一次 WAL flush)。
INSERT INTO users (email, name) VALUES ('a@x.com', 'Ada');INSERT INTO users (email, name) VALUES ('a@x.com','Ada'), ('b@x.com','Bob'), ('c@x.com','Cy');INSERT ... RETURNING取回刚插入的行,包括服务器生成的列(serial ID、默认值、生成列)。省一次 SELECT 往返。
INSERT INTO users (email) VALUES ('a@x.com') RETURNING id, created_at;INSERT INTO orders (user_id, amount) SELECT id, 0 FROM users WHERE created_at > now() - interval '1 day' RETURNING *;
INSERT ... ON CONFLICT (UPSERT)原子 upsert。命中指定 UNIQUE 约束时,DO UPDATE SET col = EXCLUDED.col 用本次想插的值,DO NOTHING 直接跳过。
⚠ 常见坑: 冲突目标必须是 UNIQUE 约束或主键 —— 普通索引不算。冲突时序列仍然前进,serial ID 会出空洞。
INSERT INTO users (email, name) VALUES ('a@x.com', 'Ada')
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;INSERT INTO event_dedupe (event_id) VALUES ('e123')
ON CONFLICT DO NOTHING;UPDATE ... RETURNING改行并返回新值。FROM 子句可以在 UPDATE 里 join 其他表(PG 对标准 SQL 的扩展)。
⚠ 常见坑: UPDATE 不带 WHERE 改全表。先跑对应的 SELECT count 看看,或者测试时 BEGIN / ROLLBACK 包起来。
UPDATE users SET name = 'Ada Lovelace' WHERE id = 1 RETURNING *;
UPDATE orders o SET status = 'paid' FROM payments p WHERE o.id = p.order_id AND p.confirmed_at IS NOT NULL RETURNING o.id;
DELETE ... RETURNING删行并返回被删的内容(适合审计日志或 "撤销" 功能)。
⚠ 常见坑: DELETE 不带 WHERE 清空表。大批量删除要分批(用 CTE 实现 DELETE WHERE id IN (...) LIMIT N 模式)—— 一次性大 DELETE 产生海量 WAL 还让表膨胀。
DELETE FROM sessions WHERE expires_at < now() RETURNING id;
WITH victims AS ( SELECT id FROM sessions WHERE expires_at < now() ORDER BY id LIMIT 10000 ) DELETE FROM sessions WHERE id IN (SELECT id FROM victims) RETURNING id;
TRUNCATE [CASCADE]瞬时清空表,不写逐行 WAL。加 RESTART IDENTITY 才重置序列。CASCADE 连带清空有外键引用的子表。
⚠ 常见坑: TRUNCATE 持 ACCESS EXCLUSIVE 锁,堵一切。某些复制拓扑里不能回滚到 savepoint。触发器默认不触发(除非显式 BEFORE TRUNCATE)。
TRUNCATE TABLE staging_imports;
TRUNCATE TABLE users RESTART IDENTITY CASCADE;
-> / ->>取 JSONB 值:-> 返回 jsonb(保留类型),->> 返回 text。要拿字符串比较或显示用 ->>。
⚠ 常见坑: 把 ->> 出来的 text 和数字比较要先转型:(data->>'count')::int > 5。不转型走 text 比较,慢且不走索引。
SELECT data->'user'->>'email' FROM events;
SELECT * FROM events WHERE (data->>'count')::int > 5;
#> / #>>路径取值:#> 返回 jsonb,#>> 返回 text。路径是数组字面量 —— '{user,name}' 或 ARRAY['user','name']。
SELECT data #> '{user,address,city}' FROM events;SELECT data #>> '{user,name}' FROM events;@>包含运算符:左侧 JSONB 是否包含右侧?可走 GIN 索引,过滤嵌套 key 的最快方式。任何 JSONB 查询负载的支柱。
SELECT * FROM events WHERE data @> '{"type":"click"}';SELECT * FROM users WHERE prefs @> '{"theme":"dark","lang":"zh"}';? / ?| / ?&? 顶层是否存在某 key。?| 是否存在这些 key 任一个。?& 是否同时存在这些 key 全部。配 jsonb_path_ops 可走 GIN 索引。
SELECT * FROM events WHERE data ? 'user_id';
SELECT * FROM events WHERE data ?| ARRAY['email','phone'];
SELECT * FROM events WHERE data ?& ARRAY['ip','ua'];
jsonb_build_object从交替的 key/value 对构造 JSONB 对象。在查询里从头构造比 jsonb_set 干净。
SELECT jsonb_build_object('id', id, 'email', email, 'created', created_at) FROM users;jsonb_set改 JSONB 指定路径的值。最后一个参数控制 key 不存在时是否创建(默认 true)。返回新 JSONB。
⚠ 常见坑: 对 NULL JSONB 跑 jsonb_set 静默返回 NULL。先 coalesce(col, '{}') 再 set,避免更新丢失。
UPDATE users SET prefs = jsonb_set(prefs, '{theme}', '"dark"') WHERE id = 1;UPDATE users SET prefs = jsonb_set(coalesce(prefs, '{}'::jsonb), '{lang}', '"zh"', true);jsonb_path_querySQL/JSON 路径查询(PG 12+):类似 JSONPath 的迷你语言,用于遍历和过滤 JSON。返回 jsonb 集合。
SELECT jsonb_path_query(data, '$.items[*] ? (@.price > 100)') FROM orders;
SELECT * FROM orders WHERE jsonb_path_exists(data, '$.items[*] ? (@.sku == "X1")');
WITH cte AS (...) SELECTCTE:给子查询命名再复用。PG 12+ 默认 inline(更快);要老的 "只执行一次缓存结果" 语义就加 MATERIALIZED。
⚠ 常见坑: PG 12 之前 CTE 是 "优化围墙",planner 不能下推谓词。老代码靠 CTE 当 hint,到 PG 12+ 这个 hint 没了,执行计划可能突然变。
WITH recent AS ( SELECT * FROM orders WHERE created_at > now() - interval '7 days' ) SELECT user_id, count(*) FROM recent GROUP BY user_id;
WITH RECURSIVE递归 CTE:遍历树和图。anchor SELECT 起步;recursive SELECT 自引用。UNION ALL 保留重复,UNION 去重。
⚠ 常见坑: 一定要有终止条件。无界递归 CTE 在循环图上一直跑到 OOM。加 WHERE depth < 100 或跟踪已访问节点。
WITH RECURSIVE org AS (
SELECT id, manager_id, name, 1 AS depth FROM employees WHERE id = 1
UNION ALL
SELECT e.id, e.manager_id, e.name, org.depth + 1
FROM employees e JOIN org ON e.manager_id = org.id
WHERE org.depth < 100
)
SELECT * FROM org;WITH writable CTECTE 可以包 INSERT / UPDATE / DELETE 配 RETURNING —— "一条语句把行从一张表搬到另一张" 的经典用法。
WITH moved AS ( DELETE FROM events WHERE created_at < now() - interval '30 days' RETURNING * ) INSERT INTO events_archive SELECT * FROM moved;
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)在每个分区内按 ORDER BY 给每行一个唯一连续编号。"每组前 N 行" 经典写法:外层套一层过滤 rn <= N。
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn <= 3;RANK() / DENSE_RANK()都在分区内排名。RANK 同分后跳号(1, 1, 3);DENSE_RANK 不跳(1, 1, 2)。"前 3 不同分数" 用 DENSE。
SELECT name, score, RANK() OVER (ORDER BY score DESC) FROM players;
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) FROM players;
LAG() / LEAD()不用自连接直接拿同一分区内的前一行(LAG)或后一行(LEAD)。第二个参数是偏移(默认 1),第三个是缺省值。
SELECT day, revenue,
revenue - LAG(revenue, 1, 0) OVER (ORDER BY day) AS day_over_day
FROM daily_revenue;NTILE(n) OVER (ORDER BY ...)按 ORDER BY 把行均匀切成 n 个桶。用于四分位、分位段、A/B 测试分组。
SELECT user_id, spend, NTILE(4) OVER (ORDER BY spend DESC) AS quartile FROM users;
sum() OVER (ROWS BETWEEN ... AND ...)帧子句:聚合看哪几行。ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW = 累计;ROWS BETWEEN 6 PRECEDING AND CURRENT ROW = 7 日滑动求和。
SELECT day, revenue,
SUM(revenue) OVER (ORDER BY day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total,
AVG(revenue) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7
FROM daily_revenue;FIRST_VALUE / LAST_VALUE取窗口帧里第一个 / 最后一个值。LAST_VALUE 要显式 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING —— 默认帧到当前行就停。
SELECT user_id,
FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS first_order,
LAST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_order
FROM orders;WINDOW w AS (...)命名窗口:OVER 子句定义一次多个聚合复用。比把 PARTITION BY x ORDER BY y 复制粘贴三次干净。
SELECT user_id,
SUM(amount) OVER w,
AVG(amount) OVER w,
COUNT(*) OVER w
FROM orders
WINDOW w AS (PARTITION BY user_id ORDER BY created_at);CREATE INDEX (B-tree)默认索引类型。等值和范围查询首选。联合索引遵守最左前缀:(a, b) 帮 WHERE a=? 和 WHERE a=? AND b=?,但不帮 WHERE b=?。
CREATE INDEX users_email_idx ON users (email);
CREATE INDEX orders_user_created_idx ON orders (user_id, created_at DESC);
CREATE INDEX ... USING GIN通用倒排索引 —— 适合 "数据本身是复合的,要查它的部件" 场景。JSONB 包含、全文 tsvector、array 包含、pg_trgm 模糊匹配。
⚠ 常见坑: GIN 写入慢(要改写整个 posting list)。写多的 JSONB 列考虑部分 GIN 或者只对热点 key 建函数索引。
CREATE INDEX events_data_gin ON events USING GIN (data jsonb_path_ops);
CREATE INDEX posts_tags_gin ON posts USING GIN (tags);
CREATE INDEX users_name_trgm ON users USING GIN (name gin_trgm_ops); -- needs pg_trgm
CREATE INDEX ... USING GiST通用搜索树 —— 几何和范围类型用。PostGIS 空间索引、range 排他约束、相似度搜索。
CREATE INDEX locations_geom_gist ON locations USING GiST (geom);
CREATE INDEX bookings_period_gist ON bookings USING GiST (period); -- daterange
partial index (WHERE)只对满足条件的行建索引。当大部分查询只命中子集(活跃行、最近行、status='pending')时,比全表索引小得多也快得多。
CREATE INDEX orders_pending_idx ON orders (created_at) WHERE status = 'pending';
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
expression / functional index索引表达式的结果,不是裸列。查询里列被函数包了就必须用(WHERE lower(email) = ? 要 lower(email) 的索引)。
CREATE INDEX users_email_lower_idx ON users (lower(email));
CREATE INDEX events_date_idx ON events (date_trunc('day', created_at));covering index (INCLUDE)PG 11+ —— 把额外的非键列也存到索引叶子页,planner 可以纯走索引(index-only scan),不回表。
⚠ 常见坑: index-only scan 跳过 heap,要可见性图是新的。批量导入后要跑 VACUUM 才能看到提速。
CREATE INDEX users_email_inc_idx ON users (email) INCLUDE (name, created_at);
CREATE INDEX ... USING BRIN块范围索引 —— 每个块范围只存 min/max 的极小索引。超大表 + 天然有序(时序、追加日志)首选。
CREATE INDEX events_created_brin ON events USING BRIN (created_at);
EXPLAIN (ANALYZE, BUFFERS)真跑一遍 SQL 并显示实际执行计划带真实行数、耗时、buffer 命中。"为什么这条慢?" 第一招 —— 看大表上的 Seq Scan、Rows Removed by Filter 巨大、读盘数高。
⚠ 常见坑: EXPLAIN ANALYZE 跑 UPDATE / DELETE / INSERT 会真写数据。包在 BEGIN; EXPLAIN ANALYZE ...; ROLLBACK; 里只看计划不改数据。
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email = $1;
BEGIN; EXPLAIN (ANALYZE) UPDATE users SET name = 'x' WHERE id = 1; ROLLBACK;
PARTITION BY RANGE按值范围切表,最常见按日期。每个分区是独立的物理表 —— 删一个分区 = 毫秒级删掉一整个月数据。
CREATE TABLE events (id bigserial, created_at timestamptz NOT NULL, payload jsonb)
PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_05 PARTITION OF events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');PARTITION BY LIST按显式枚举值切表。多租户按客户分区、按地区分表都用它。
CREATE TABLE orders (id bigserial, region text, amount numeric)
PARTITION BY LIST (region);
CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('us-east','us-west');
CREATE TABLE orders_eu PARTITION OF orders FOR VALUES IN ('eu-west','eu-central');PARTITION BY HASH对分区键做哈希,行均匀分到 N 个分区。没有天然的 range / list 但想分散写入压力时用。
CREATE TABLE messages (id bigint, user_id bigint, body text) PARTITION BY HASH (user_id); CREATE TABLE messages_p0 PARTITION OF messages FOR VALUES WITH (MODULUS 4, REMAINDER 0); CREATE TABLE messages_p1 PARTITION OF messages FOR VALUES WITH (MODULUS 4, REMAINDER 1);
ATTACH / DETACH PARTITION把一张表 attach / detach 到分区表。DETACH 是 "便宜地删掉一个月数据" 的关键操作。PG 14+ 的 CONCURRENTLY 避免短暂的 access exclusive 锁。
⚠ 常见坑: ATTACH PARTITION 默认会全表扫验证分区范围 —— 除非表上已经有匹配的 CHECK 约束。先加 CHECK 再 ATTACH 就能跳过扫描。
ALTER TABLE events ATTACH PARTITION events_2026_06 FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');ALTER TABLE events DETACH PARTITION events_2025_01 CONCURRENTLY;
pg_dump数据库的逻辑备份:默认 SQL 文本,可选 custom (-Fc) / directory (-Fd) / tar (-Ft) 格式支持并行恢复。任何客户端都能跑;跨集群迁移用 --no-owner / --no-privileges。
⚠ 常见坑: pg_dump 是单事务快照一致的,但多 TB 库就慢。超大集群用 pg_basebackup(物理备份)+ WAL 归档,不用 pg_dump。
pg_dump -Fc -f app.dump app
pg_dump -Fd -j 4 -f app_dir app # 4-way parallel dump
pg_dump --schema-only -f schema.sql app
pg_restore从 pg_dump 归档(-Fc / -Fd / -Ft 格式)恢复。-j 并行恢复,-t 按表过滤,--no-owner 跨用户迁移。
pg_restore -d app_new -j 4 app.dump
pg_restore -d app -t users app.dump
pg_restore --schema-only -d app schema.dump
pg_basebackup整个集群的物理基础备份 —— 服务器照常运行的同时按字节复制数据目录。流复制和 PITR 的基础。
pg_basebackup -h primary.db -D /var/lib/postgresql/replica -U replicator -R -P
streaming replication备库连主库实时回放 WAL。备库配 primary_conninfo,主库建复制槽(replication slot)保证 WAL 留到备库消费完为止。
⚠ 常见坑: 不建复制槽,主库可能在备库读到之前回收 WAL —— 备库永久落后。建了槽,备库挂了主库磁盘就被撑爆。监控 pg_replication_slots.confirmed_flush_lsn。
SELECT pg_create_physical_replication_slot('replica1');SELECT slot_name, active, confirmed_flush_lsn FROM pg_replication_slots;
logical replication跨 PG 版本复制指定的表(不是整个集群)。基于 publication(源)和 subscription(目标)。大版本在线升级的必备工具。
CREATE PUBLICATION mypub FOR TABLE users, orders;
CREATE SUBSCRIPTION mysub CONNECTION 'host=src dbname=app user=repl' PUBLICATION mypub;
CREATE ROLE / CREATE USERPG 里 USER 和 GROUP 是一回事:都是 ROLE。CREATE USER = CREATE ROLE ... LOGIN。角色可以授予其他角色(PG 风格的组成员关系)。
CREATE ROLE app_readonly NOLOGIN;
CREATE USER app_service WITH PASSWORD 'xxx';
GRANT app_readonly TO app_service;
GRANT / REVOKE授予 / 收回数据库、schema、表、列、序列、函数权限。新建的表不会自动继承权限 —— 用 ALTER DEFAULT PRIVILEGES 让授权对未来对象也生效。
⚠ 常见坑: GRANT SELECT ON ALL TABLES IN SCHEMA 只覆盖现在存在的表,以后建的没权限。ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly 才让未来建的表也有权限。
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
pg_hba.confhost 认证配置:把 (连接类型, 数据库, 用户, 地址) 映射到认证方法。顺序敏感 —— 命中第一条就停。方法:trust(不需要密码!)、md5、scram-sha-256、peer、cert、ldap。
⚠ 常见坑: 一条漏写的 host all all 0.0.0.0/0 trust 让全网随便登录任何用户。所有集群审计 pg_hba.conf —— 任何能从网络到达的行不准用 trust。
# TYPE DATABASE USER ADDRESS METHOD
host all all 10.0.0.0/8 scram-sha-256
hostssl app app 0.0.0.0/0 scram-sha-256
local all all peer
ROW LEVEL SECURITY (RLS)行级安全策略 —— 数据库自己保证 "用户 X 只看到自己的行"。表上启用后 CREATE POLICY。
⚠ 常见坑: RLS 默认对表 owner 和 superuser 不生效。要 FORCE ROW LEVEL SECURITY 才对 owner 也生效。否则迁移和以 owner 身份连接的应用都绕过你的安全。
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
ALTER TABLE notes FORCE ROW LEVEL SECURITY;
CREATE POLICY my_notes ON notes
USING (user_id = current_setting('app.user_id')::bigint);pg_stat_statements每条 SQL 的执行统计:总耗时、平均耗时、调用次数、返回行数、I/O。生产集群第一个装的扩展 —— 没有它 "找慢 SQL" 全靠猜。
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- needs shared_preload_libraries
SELECT query, calls, mean_exec_time, total_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;
pg_trgm基于 trigram 的模糊文本匹配。让 LIKE '%foo%'、ILIKE 和相似度搜索(% 运算符)可走 GIN 索引。Search-as-you-type 的关键扩展。
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX users_name_trgm ON users USING GIN (name gin_trgm_ops);
SELECT * FROM users WHERE name % 'Adda'; -- fuzzy match
postgis地理空间数据和查询:geometry / geography 类型、空间索引、距离 / 包含 / 相交运算符。严肃 GIS 工作负载选 PostgreSQL 的核心原因。
CREATE EXTENSION IF NOT EXISTS postgis;
SELECT name FROM places WHERE ST_DWithin(geog, ST_MakePoint(-122.4, 37.8)::geography, 1000); -- within 1km
uuid-osspUUID 生成函数(v1、v3、v4、v5)。PG 13+ 自带 gen_random_uuid() 覆盖 v4 —— 只在需要 v1(时间戳)或 v5(命名空间)时才装 uuid-ossp。
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
SELECT uuid_generate_v1();
SELECT uuid_generate_v5(uuid_ns_url(), 'https://x.com/user/42');
hstore / pgcrypto / citexthstore:简单键值对(jsonb 之前的产物,新代码用 jsonb)。pgcrypto:crypt()、gen_salt()、digest()、pgp_sym_encrypt()。citext:不区分大小写的 text 类型 —— 比到处写 lower() 更友好可索引。
CREATE EXTENSION IF NOT EXISTS pgcrypto;
INSERT INTO users (password) VALUES (crypt('hunter2', gen_salt('bf')));SELECT * FROM users WHERE password = crypt('hunter2', password);CREATE EXTENSION IF NOT EXISTS citext;
CREATE TABLE accounts (email citext UNIQUE);
NULL is not equal to NULLSQL 里 NULL 表示 "未知"。NULL = NULL 结果是 NULL(不是 TRUE),NULL != NULL 也是 NULL。要用 IS NULL / IS NOT NULL,或者 IS DISTINCT FROM 把 NULL 当可比较的值。
⚠ 常见坑: WHERE col = NULL 永远零行。NOT IN (子查询) 只要子查询里有一个 NULL 整体返空。改 NOT EXISTS 或者先过滤掉 NULL。
SELECT * FROM users WHERE deleted_at IS NULL;
SELECT * FROM users WHERE id IS DISTINCT FROM other_id; -- treats NULL as a value
SELECT * FROM orders WHERE NOT EXISTS (SELECT 1 FROM refunds WHERE refunds.order_id = orders.id);
VACUUM bloatPG 用 MVCC:UPDATE = 插入新行 + 标记旧行 dead。VACUUM 回收 dead 行;不跑表会一直涨且查询变慢。autovacuum 默认开 —— 但高写入表可能要调参。
⚠ 常见坑: 长事务(忘了关的 psql、卡死的连接)会全集群堵 vacuum。SELECT * FROM pg_stat_activity WHERE state = 'idle in transaction' 查一下,杀掉老会话。
VACUUM ANALYZE users;
VACUUM FULL users; -- rewrites table, exclusive lock — avoid in production
SELECT pid, age(now(), xact_start) AS age, query FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY age DESC;
big-table ALTER多数 ALTER 持 ACCESS EXCLUSIVE 锁 —— 整段时间堵读堵写。生产忙表上锁一秒就能级联出几千个卡住的查询。永远要预估锁时间。
⚠ 常见坑: PG 11+ ADD COLUMN 非 volatile 默认值只动元数据。volatile 默认值(gen_random_uuid())仍会重写表。ALTER COLUMN TYPE 基本都会重写。要提前规划。
SET lock_timeout = '5s'; -- never block forever
ALTER TABLE users ADD COLUMN x text; -- safe, metadata only
ALTER TABLE users ADD COLUMN id_v2 uuid DEFAULT gen_random_uuid(); -- REWRITES TABLE — danger
sequence drift序列在事务外。回滚的 INSERT 也消耗 ID。COPY 批量导入可能让序列值落后于实际 max(id) —— 下次 INSERT 报主键冲突。
⚠ 常见坑: 显式带 id 列批量导入后,跑 SELECT setval('users_id_seq', (SELECT max(id) FROM users)) 把序列拨回去。
SELECT setval('users_id_seq', (SELECT max(id) FROM users));SELECT last_value, is_called FROM users_id_seq;
WAL filling the diskWAL(预写日志)会堆积:archive_command 失败、复制槽没人消费、wal_keep_size 太大都会。WAL 卷写满直接挂服务器。监控 pg_wal 目录大小。
⚠ 常见坑: 永远不要手动 rm pg_wal 里的文件 —— 直接腐烂数据库。用 pg_archivecleanup 或者干掉惹事的复制槽(SELECT pg_drop_replication_slot('slot_name'))。
SELECT slot_name, active, restart_lsn, pg_size_pretty(pg_current_wal_lsn() - restart_lsn) AS lag FROM pg_replication_slots;
SELECT pg_drop_replication_slot('dead_replica');timezone surprisestimestamp without time zone 只存裸 y-m-d h:m:s,不带时区。应用和数据库时区不一致时,每次读都静默把时间理解错。一律 timestamptz;时区只在展示边界 SET TIME ZONE。
SHOW timezone;
SET TIME ZONE 'Asia/Shanghai';
SELECT created_at AT TIME ZONE 'UTC' FROM orders; -- explicit conversion
search_path securityPG 解析没限定 schema 的名字时走 search_path。如果 search_path 包含 public,攻击者能在 public 里建对象 shadow 你的函数和表。一律 set search_path = "$user", pg_catalog,SECURITY DEFINER 函数里所有名字都全限定。
⚠ 常见坑: SECURITY DEFINER 函数不固定 search_path 就是 CVE 制造机。永远写:CREATE FUNCTION ... SECURITY DEFINER SET search_path = pg_catalog, pg_temp。
SHOW search_path;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
CREATE FUNCTION sensitive() RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp AS $$ BEGIN /* ... */ END $$;
可搜索的 PostgreSQL 速查表,覆盖后端、DBA、数据岗日常真在 psql 里敲的 80+ 条命令和函数,不是凑数的入门 SELECT 列表。 十三大分类:psql 元命令(\l \dt \d \du \q \i \timing \x \! \conninfo \copy \dn \df),数据类型(text 与 varchar 怎么 选、int / bigint / serial / bigserial / identity、uuid、 timestamp 与 timestamptz、interval、jsonb 与 json、array、 enum、range),DDL(CREATE / ALTER / DROP,IF EXISTS 与 CASCADE,生成列,表继承),DML(INSERT、UPDATE、DELETE、 RETURNING、INSERT ... ON CONFLICT 原子 UPSERT),JSONB 操作(-> ->> #> #>> @> ? ?| ?& || jsonb_build_object jsonb_set jsonb_path_query,配合 GIN 索引),CTE(WITH ... AS,RECURSIVE 递归遍树,MATERIALIZED 提示),窗口函数 (ROW_NUMBER、RANK、DENSE_RANK、LAG、LEAD、NTILE、ROWS 与 RANGE 帧,FIRST_VALUE,命名 WINDOW 子句),索引(B 树、 GIN、GiST、BRIN,部分索引,表达式 / 函数索引,覆盖索引 INCLUDE,CREATE INDEX CONCURRENTLY),表分区(PARTITION BY RANGE / LIST / HASH,attach / detach,分区裁剪),复制与备 份(pg_dump、pg_restore、pg_basebackup,流复制,逻辑复制, WAL),角色与权限(CREATE ROLE、GRANT、REVOKE、pg_hba.conf 认证方式、search_path、行级安全 RLS),常用扩展(pg_stat_ statements、pg_trgm、postgis、uuid-ossp、hstore、pgcrypto、 citext),以及真烧钱的坑(NULL 比较、vacuum bloat、大表 ALTER、序列空洞、WAL 写满磁盘、时区惊喜、search_path 安全 问题)。每条都附中英说明、1-3 条可直接粘到 psql 跑的真实 例子、一行"常见坑"。搜索框跨命令 / 说明 / 例子 / 坑四个 字段一起搜,分类胶囊缩范围。完全在浏览器里跑,不连库,不 上传,不发任何网络请求。配合 SQL Formatter、SQL 速查搭配 Docker / kubectl / Regex 速查覆盖整条技术栈。
把内容粘贴或拖入工具面板。
点击按钮,在浏览器内本地处理,文件不上传。
一键复制结果或下载到本地。
适合穿插在写代码、查问题、做 Review、上线前的小任务里。
这些入口会把当前任务接到更完整的工具链里。
做你这行的人, 还会一起用这些。