PostgreSQL(常简称 Postgres)是世界上最先进的开源关系型数据库管理系统。它起源于 1986 年加州大学伯克利分校的 POSTGRES 项目,经过 40 多年的持续开发,已成为企业级应用的首选数据库之一。
PostgreSQL 以其卓越的数据完整性、丰富的功能集、可扩展性和标准兼容性而著称。它支持 JSON/JSONB、全文搜索、地理空间数据 (PostGIS)、窗口函数、CTE、表分区等高级特性,被广泛应用于金融、电信、政府、互联网等各个领域。
| 项目信息 | 详情 |
|---|---|
| 开发团队 | PostgreSQL Global Development Group |
| GitHub Stars | 17,000+ |
| 官方网站 | postgresql.org |
| 开源协议 | PostgreSQL License(类 BSD/MIT,非常宽松) |
| 核心语言 | C |
| 历史 | 40+ 年(1986 年起源于 UC Berkeley) |
| 最新稳定版 | PostgreSQL 17(2024 年 9 月发布) |
| 对比维度 | PostgreSQL | MySQL | SQLite | MongoDB |
|---|---|---|---|---|
| 类型 | 关系型 (ORDBMS) | 关系型 (RDBMS) | 嵌入式关系型 | 文档型 (NoSQL) |
| ACID 合规 | 完全支持 | InnoDB 支持 | 完全支持 | 4.0+ 多文档事务 |
| JSON 支持 | 原生 JSONB,可索引 | JSON 类型,功能较少 | JSON 函数有限 | 原生文档模型 |
| 全文搜索 | 内置 tsvector/tsquery | InnoDB 全文索引 | FTS5 扩展 | Atlas Search |
| 扩展性 | 插件生态极丰富 | 存储引擎可选 | 嵌入式,不可扩展 | 分片内置 |
| 并发性能 | MVCC,高并发读写 | InnoDB MVCC | 写锁,单写并发低 | 文档级锁 |
| SQL 标准兼容 | 最接近 SQL 标准 | 部分兼容 | 部分兼容 | MQL(非 SQL) |
| 学习曲线 | 中等 | 简单 | 极简 | 中等 |
| 适用场景 | 企业级、复杂查询、GIS | Web 应用、LAMP 栈 | 嵌入式、移动端、小项目 | 灵活 schema、大数据量 |
# 添加 PostgreSQL 官方仓库 sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - # 更新并安装 sudo apt update sudo apt install -y postgresql-17 # 检查状态 sudo systemctl status postgresql # 切换到 postgres 用户 sudo -u postgres psql
sudo yum install -y postgresql17-server,安装后需运行 postgresql-17-setup initdb 初始化。# 安装 brew install postgresql@17 # 启动服务 brew services start postgresql@17 # 连接 psql postgres也可以使用图形化安装器 Postgres.app,下载后拖入应用程序文件夹即可。
# Windows 命令行连接 psql -U postgres -h localhost
# 快速启动 docker run -d --name postgres \ -e POSTGRES_PASSWORD=mysecretpassword \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql/data \ postgres:17-alpine # 连接 docker exec -it postgres psql -U postgresDocker Compose 部署(生产推荐):
shm_size 建议设为物理内存的 25%。CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email TEXT NOT NULL,
profile JSONB DEFAULT '{}',
tags TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
is_active BOOLEAN DEFAULT true,
balance NUMERIC(12,2) DEFAULT 0.00
);
常用特殊类型:
SERIAL / BIGSERIAL — 自增主键
JSONB — 二进制 JSON,可索引,推荐替代 JSON
TEXT[] — 数组类型
TIMESTAMPTZ — 带时区的时间戳
UUID — 通用唯一标识符
INET / CIDR — IP 地址类型
NUMERIC — 精确小数,适合金融
-- B-Tree 索引(默认,适合等值和范围查询) CREATE INDEX idx_users_email ON users(email); -- GIN 索引(适合 JSONB、数组、全文搜索) CREATE INDEX idx_users_profile ON users USING GIN(profile); -- GiST 索引(适合地理空间、范围类型) CREATE INDEX idx_geo ON locations USING GiST(coordinates); -- BRIN 索引(适合时序数据,极小体积) CREATE INDEX idx_logs_time ON logs USING BRIN(created_at); -- 部分索引(只索引满足条件的行) CREATE INDEX idx_active_users ON users(email) WHERE is_active = true; -- 表达式索引 CREATE INDEX idx_lower_email ON users(LOWER(email));
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
amount NUMERIC(12,2) CHECK (amount > 0),
status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending','paid','shipped','done','cancelled')),
order_no VARCHAR(32) UNIQUE NOT NULL,
CONSTRAINT positive_amount CHECK (amount > 0)
);
-- 排他约束(防止会议室时间冲突)
CREATE TABLE reservations (
room_id INT,
during TSTZRANGE,
EXCLUDE USING GiST (room_id WITH =, during WITH &&)
);
常用约束类型:PRIMARY KEY、UNIQUE、NOT NULL、CHECK、FOREIGN KEY、EXCLUDE。
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 检查余额
DO $$
BEGIN
IF (SELECT balance FROM accounts WHERE id = 1) < 0 THEN
RAISE EXCEPTION '余额不足';
END IF;
END $$;
COMMIT;
-- 发生错误自动回滚
-- 也可以用 SAVEPOINT 做部分回滚
BEGIN;
SAVEPOINT sp1;
INSERT INTO logs VALUES ('step1');
SAVEPOINT sp2;
INSERT INTO logs VALUES ('step2'); -- 假设这里出错
ROLLBACK TO sp2; -- 只回滚 step2
COMMIT; -- step1 仍然提交
SET TRANSACTION ISOLATION LEVEL 设置。xmin(创建事务 ID)和 xmax(删除事务 ID)列:
VACUUM 的原因 — 清理死元组(dead tuples),释放空间。
-- 创建 schema CREATE SCHEMA app; CREATE SCHEMA analytics; -- 在指定 schema 下建表 CREATE TABLE app.users (id SERIAL PRIMARY KEY, name TEXT); CREATE TABLE analytics.events (id SERIAL PRIMARY KEY, data JSONB); -- 设置搜索路径 SET search_path TO app, public; -- 查看当前搜索路径 SHOW search_path;
public
search_path 决定了不指定 schema 时的查找顺序
-- 基本 CTE
WITH active_users AS (
SELECT id, username, email
FROM users
WHERE is_active = true AND created_at > NOW() - INTERVAL '30 days'
),
user_orders AS (
SELECT user_id, COUNT(*) as order_count, SUM(amount) as total
FROM orders
WHERE user_id IN (SELECT id FROM active_users)
GROUP BY user_id
)
SELECT u.username, u.email, COALESCE(o.order_count, 0) as orders, COALESCE(o.total, 0) as spent
FROM active_users u
LEFT JOIN user_orders o ON u.id = o.user_id
ORDER BY spent DESC;
-- 递归 CTE(遍历树形结构)
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 1 as depth
FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.depth + 1
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY depth, name;
-- 排名:每个部门内按薪资排名
SELECT name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_with_ties,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank
FROM employees;
-- 累计求和
SELECT date, revenue,
SUM(revenue) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_total
FROM daily_sales;
-- 同比/环比
SELECT month, revenue,
LAG(revenue, 1) OVER (ORDER BY month) as prev_month,
revenue - LAG(revenue, 1) OVER (ORDER BY month) as mom_change,
LAG(revenue, 12) OVER (ORDER BY month) as prev_year,
revenue - LAG(revenue, 12) OVER (ORDER BY month) as yoy_change
FROM monthly_revenue;
-- 查询 JSONB 字段
SELECT * FROM users WHERE profile->>'city' = '北京';
SELECT * FROM users WHERE profile @> '{"level": "vip"}';
-- JSONB 路径查询(PostgreSQL 12+)
SELECT * FROM users WHERE profile @? '$.addresses[*] ? (@.city == "上海")';
-- 更新 JSONB 字段
UPDATE users SET profile = profile || '{"verified": true}' WHERE id = 1;
UPDATE users SET profile = jsonb_set(profile, '{address,city}', '"深圳"') WHERE id = 1;
-- JSONB 聚合
SELECT profile->>'city' as city, COUNT(*) as user_count
FROM users
WHERE profile ? 'city'
GROUP BY profile->>'city'
ORDER BY user_count DESC;
-- JSONB 展开为行
SELECT id, elem->>'name' as tag_name
FROM users, jsonb_array_elements(profile->'tags') as elem;
CREATE INDEX idx_profile ON users USING GIN(profile)。-- 添加 tsvector 列并建立索引
ALTER TABLE articles ADD COLUMN search_vector TSVECTOR;
UPDATE articles SET search_vector =
setweight(to_tsvector('chinese', title), 'A') ||
setweight(to_tsvector('chinese', content), 'B');
CREATE INDEX idx_search ON articles USING GIN(search_vector);
-- 全文搜索查询
SELECT title, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('chinese', '数据库 & 优化') as query
WHERE search_vector @@ query
ORDER BY rank DESC;
-- 搜索结果高亮
SELECT ts_headline('chinese', content, to_tsquery('chinese', '数据库'),
'StartSel=<mark>, StopSel=</mark>, MaxWords=50') as snippet
FROM articles
WHERE search_vector @@ to_tsquery('chinese', '数据库');
zhparser 或 pg_jieba 分词插件。默认分词器不支持中文。-- 创建范围分区表(按时间)
CREATE TABLE logs (
id BIGSERIAL,
msg TEXT,
level VARCHAR(10),
ts TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (ts);
-- 创建分区
CREATE TABLE logs_2025q1 PARTITION OF logs FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');
CREATE TABLE logs_2025q2 PARTITION OF logs FOR VALUES FROM ('2025-04-01') TO ('2025-07-01');
CREATE TABLE logs_2025q3 PARTITION OF logs FOR VALUES FROM ('2025-07-01') TO ('2025-10-01');
CREATE TABLE logs_2025q4 PARTITION OF logs FOR VALUES FROM ('2025-10-01') TO ('2026-01-01');
-- 在分区表上创建索引(自动应用到所有分区)
CREATE INDEX idx_logs_ts ON logs(ts);
-- 删除旧分区(比 DELETE 快得多)
DROP TABLE logs_2024q1;
分区策略:RANGE(时间序列)、LIST(枚举值分类)、HASH(均匀分布)。
-- 每个用户最近 3 条订单
SELECT u.username, r.order_no, r.amount, r.created_at
FROM users u
CROSS JOIN LATERAL (
SELECT order_no, amount, created_at
FROM orders
WHERE user_id = u.id
ORDER BY created_at DESC
LIMIT 3
) r;
-- 比传统方式(窗口函数 + 子查询)更直观高效
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC(10,2),
tax_rate NUMERIC(4,2) DEFAULT 0.13,
total NUMERIC(10,2) GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED,
full_name TEXT GENERATED ALWAYS AS (brand || ' ' || model) STORED,
brand TEXT,
model TEXT
);
STORED 表示值物理存储在磁盘上,插入/更新时自动计算。
-- 存在则更新
INSERT INTO user_stats (user_id, login_count, last_login)
VALUES (1, 1, NOW())
ON CONFLICT (user_id)
DO UPDATE SET
login_count = user_stats.login_count + 1,
last_login = EXCLUDED.last_login;
-- 存在则跳过
INSERT INTO tags (name)
VALUES ('postgresql'), ('database'), ('sql')
ON CONFLICT (name) DO NOTHING;
-- 批量 UPSERT(配合 RETURNING)
INSERT INTO inventory (sku, quantity)
VALUES ('A001', 10), ('A002', 20), ('A003', 30)
ON CONFLICT (sku)
DO UPDATE SET quantity = inventory.quantity + EXCLUDED.quantity
RETURNING *;
-- 创建物化视图
CREATE MATERIALIZED VIEW mv_daily_stats AS
SELECT date_trunc('day', created_at) as day,
COUNT(*) as total_orders,
SUM(amount) as total_revenue,
AVG(amount) as avg_order_value
FROM orders
GROUP BY date_trunc('day', created_at)
WITH DATA;
-- 在物化视图上建索引
CREATE UNIQUE INDEX idx_mv_daily ON mv_daily_stats(day);
-- 刷新(全量重算)
REFRESH MATERIALIZED VIEW mv_daily_stats;
-- 并发刷新(不阻塞读,需要唯一索引)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_stats;
-- 创建函数
CREATE OR REPLACE FUNCTION transfer_funds(
from_id BIGINT,
to_id BIGINT,
amount NUMERIC
) RETURNS VOID AS $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE id = from_id FOR UPDATE;
IF current_balance < amount THEN
RAISE EXCEPTION '余额不足:当前 %, 需要 %', current_balance, amount;
END IF;
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
INSERT INTO transactions (from_id, to_id, amount, ts)
VALUES (from_id, to_id, amount, NOW());
END;
$$ LANGUAGE plpgsql;
-- 调用
SELECT transfer_funds(1, 2, 100.00);
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT u.username, COUNT(o.id) as order_count FROM users u JOIN orders o ON u.id = o.user_id WHERE u.created_at > '2025-01-01' GROUP BY u.username ORDER BY order_count DESC LIMIT 10;关键指标解读:
Seq Scan — 全表扫描(大表应避免)
Index Scan — 索引扫描(理想)
Bitmap Index Scan — 位图索引扫描(多条件 OR 查询)
actual time — 实际执行时间(毫秒)
rows — 实际返回行数 vs 估计行数(差异大说明统计信息不准)
Buffers: shared hit / read — 缓存命中 vs 磁盘读取
-- 1. 复合索引(遵循最左前缀原则)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);
-- 2. 覆盖索引(INCLUDE 避免回表)
CREATE INDEX idx_orders_cover ON orders(user_id) INCLUDE (amount, status);
-- 3. 查找未使用的索引
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexname NOT LIKE '%pkey%'
ORDER BY pg_relation_size(indexrelid) DESC;
-- 4. 查找缺失索引(慢查询中的 Seq Scan)
SELECT relname, seq_scan, seq_tup_read,
idx_scan, seq_tup_read / GREATEST(seq_scan, 1) as avg_seq_rows
FROM pg_stat_user_tables
WHERE seq_scan > 100 AND seq_tup_read / GREATEST(seq_scan, 1) > 1000
ORDER BY seq_tup_read DESC;
DROP INDEX CONCURRENTLY idx_name;# 安装 PgBouncer sudo apt install -y pgbouncer
-- 手动 VACUUM
VACUUM VERBOSE users; -- 回收空间(不锁表)
VACUUM FULL users; -- 重写表(锁表,释放磁盘空间)
VACUUM ANALYZE users; -- 回收空间 + 更新统计信息
-- 查看表膨胀情况
SELECT relname,
n_dead_tup,
n_live_tup,
round(n_dead_tup * 100.0 / GREATEST(n_live_tup + n_dead_tup, 1), 1) as dead_pct,
last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Autovacuum 关键参数调优:
postgresql.conf 核心参数:
# 备份单个数据库(自定义格式,支持并行恢复) pg_dump -Fc -j 4 -f mydb.dump mydb # 备份为 SQL 文件 pg_dump -f mydb.sql mydb # 仅备份 schema(不含数据) pg_dump --schema-only -f schema.sql mydb # 仅备份数据 pg_dump --data-only -f data.sql mydb # 备份指定表 pg_dump -t users -t orders -f tables.dump mydb # 备份所有数据库 pg_dumpall -f all_databases.sql # 恢复 pg_restore -d mydb -j 4 mydb.dump # 自定义格式 psql -d mydb -f mydb.sql # SQL 格式
# 基础物理备份 pg_basebackup -D /backup/base -Ft -z -P -U replicator # 参数说明: # -D: 备份目录 # -Ft: tar 格式 # -z: gzip 压缩 # -P: 显示进度 # -U: 复制用户 # 搭建流复制备库 pg_basebackup -D /var/lib/postgresql/17/standby \ -h primary_host -p 5432 -U replicator -R -P
-R 参数会自动生成 standby.signal 和连接配置,备库启动后自动追赶主库。
# 1. 确保有基础备份 + WAL 归档 # 2. 创建恢复配置
# 3. 创建 recovery.signal 文件 touch /var/lib/postgresql/17/main/recovery.signal # 4. 启动 PostgreSQL,自动回放 WAL 到目标时间点 sudo systemctl start postgresql
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'newpassword';"
pg_hba.conf 中的认证方式,确保对应行使用 md5 或 scram-sha-256
SELECT pg_reload_conf();
pg_hba.conf 添加规则:
# 允许特定 IP host mydb myuser 192.168.1.0/24 scram-sha-256 # 允许所有 IP(不推荐用于生产) host all all 0.0.0.0/0 scram-sha-256修改后执行
SELECT pg_reload_conf(); 或 systemctl reload postgresql。
sudo -u myuser psql
pg_hba.conf 将 peer 改为 md5:
# 将 local all all peer # 改为 local all all md5然后重新加载配置。
SELECT count(*) FROM pg_stat_activity WHERE usename = 'myuser';
ALTER ROLE myuser CONNECTION LIMIT 100;
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename = 'myuser' AND state = 'idle' AND query_start < NOW() - INTERVAL '10 minutes';
sudo -u postgres psql SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND pid != pg_backend_pid();
postgresql.conf 中 max_connections = 300(需重启)
sudo systemctl status postgresql
sudo systemctl start postgresql
postgresql.conf 中 listen_addresses = '*'(默认只监听 localhost)
ss -tlnp | grep 5432
psql "host=myhost dbname=mydb sslmode=require"
pg_hba.conf 将 hostssl 改为 host(不推荐,降低安全性)
?sslmode=require
psql -U postgres -l
CREATE DATABASE myapp OWNER myuser;
\dt *users*
\dt *.users
SET search_path TO myschema, public;
SELECT * FROM myschema.users;
GROUP BY id, name
MAX(name)、STRING_AGG(name, ',')
WHERE id = '123'::integer 或 WHERE id::text = '123'
WHERE CAST(id AS text) = '123'
SELECT * FROM pg_stat_activity WHERE wait_event_type = 'Lock';
log_lock_waits = on,deadlock_timeout = 1s
ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(100);
TEXT 类型(无长度限制,性能与 VARCHAR 相同)
INSERT ... ON CONFLICT (email) DO UPDATE SET ...
ON CONFLICT DO NOTHING 跳过重复
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
ALTER TABLE users ALTER COLUMN email SET DEFAULT '';
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
ALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;
ALTER TABLE orders DISABLE TRIGGER ALL;(导入后务必重新启用)
ROLLBACK; 结束当前事务
SAVEPOINT 做部分回滚可以避免此问题
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs
WHERE created_at < '2025-01-01'
ORDER BY created_at
LIMIT 1000
);
-- 错误 SELECT id, name FROM users u JOIN orders o ON u.id = o.user_id; -- 正确 SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id;
CREATE OR REPLACE。
DROP FUNCTION my_func(INT, TEXT);(需要指定参数类型)
CASCADE:DROP FUNCTION my_func(INT, TEXT) CASCADE;
ANALYZE table_name;
default_statistics_target,对关键列提高精度:ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000;
REINDEX INDEX idx_name;
CREATE STATISTICS st ON col1, col2 FROM table;
-- 查看锁等待关系
SELECT blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_locks bl ON blocked.pid = bl.pid AND NOT bl.granted
JOIN pg_locks gl ON bl.locktype = gl.locktype
AND bl.database IS NOT DISTINCT FROM gl.database
AND bl.relation IS NOT DISTINCT FROM gl.relation
AND bl.page IS NOT DISTINCT FROM gl.page
AND bl.tuple IS NOT DISTINCT FROM gl.tuple
AND bl.pid != gl.pid AND gl.granted
JOIN pg_stat_activity blocking ON gl.pid = blocking.pid;
-- 终止阻塞进程
SELECT pg_terminate_backend(blocking_pid);
预防:设置 lock_timeout = '5s'、statement_timeout = '30s'。
SELECT relname, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables;
VACUUM VERBOSE table_name;
VACUUM FULL(会锁表)或使用 pg_repack(不锁表):
# 安装 pg_repack sudo apt install postgresql-17-repack # 在线重建表(不锁表) pg_repack -d mydb -t bloated_table
work_mem 不够,排序或哈希操作溢出到磁盘。
work_mem(注意是每个操作分配,不要全局设太大):
-- 仅对当前会话增大 SET work_mem = '256MB'; -- 或全局调整(postgresql.conf) work_mem = '64MB'
EXPLAIN (ANALYZE, BUFFERS) 确认内存使用情况。
SELECT pid, now() - query_start AS duration, state, query FROM pg_stat_activity WHERE state = 'active' AND pid != pg_backend_pid() ORDER BY duration DESC;
pg_stat_statements 找出最耗资源的查询:
CREATE EXTENSION pg_stat_statements; SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;
CREATE INDEX 会获取表的写锁。在生产环境使用并发创建:
-- 并发创建索引(不锁表,但更慢) CREATE INDEX CONCURRENTLY idx_name ON table_name(column); -- 如果失败,会留下 INVALID 索引,需要删除后重建 DROP INDEX CONCURRENTLY idx_name; CREATE INDEX CONCURRENTLY idx_name ON table_name(column);
CONCURRENTLY 不能在事务块中使用,且需要执行两次表扫描,时间大约是普通创建的 2-3 倍。max_wal_size:max_wal_size = '4GB'(默认 1GB)
checkpoint_completion_target = 0.9(平摊 IO)
log_checkpoints = on 查看 checkpoint 频率和耗时。
SELECT setval('my_table_id_seq', (SELECT MAX(id) FROM my_table));
idle_session_timeout = '10min'
-- 终止空闲超过 30 分钟的连接 SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND query_start < NOW() - INTERVAL '30 minutes';
shared_buffers(不超过 RAM 的 25%)
work_mem(考虑并发连接数 * work_mem)
max_connections(每个连接约占 5-10MB)
vm.overcommit_memory = 2
vm.overcommit_ratio = 80
DROP TABLE users CASCADE;(会删除所有依赖对象)
ALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;
SELECT dependent_ns.nspname, dependent_view.relname FROM pg_depend JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid JOIN pg_class dependent_view ON pg_rewrite.ev_class = dependent_view.oid JOIN pg_namespace dependent_ns ON dependent_view.relnamespace = dependent_ns.oid WHERE pg_depend.refobjid = 'users'::regclass;
ALTER TABLE t ADD COLUMN c TYPE;
ALTER TABLE t ALTER COLUMN c SET DEFAULT value;
-- 分批更新避免长事务
DO $$
DECLARE batch_size INT := 10000; affected INT;
BEGIN
LOOP
UPDATE t SET c = default_value WHERE c IS NULL AND id IN (
SELECT id FROM t WHERE c IS NULL LIMIT batch_size
);
GET DIAGNOSTICS affected = ROW_COUNT;
EXIT WHEN affected = 0;
PERFORM pg_sleep(0.1);
END LOOP;
END $$;
COPY 命令批量导入,比 INSERT 快 10-100 倍:
# 从 CSV 导入 COPY users(name, email, age) FROM '/tmp/users.csv' WITH (FORMAT csv, HEADER true); # 从标准输入导入 cat data.csv | psql -c "COPY users FROM STDIN WITH (FORMAT csv, HEADER true)" mydb # 加速技巧 # 1. 导入前禁用索引和约束 # 2. 增大 maintenance_work_mem # 3. 设置 synchronous_commit = off(注意风险) # 4. 导入后重建索引和 ANALYZE
AUTO_INCREMENT → SERIAL 或 GENERATED ALWAYS AS IDENTITY
IFNULL() → COALESCE()
GROUP_CONCAT() → STRING_AGG()
LIMIT x, y → LIMIT y OFFSET x
`column` → 双引号 "column"
NOW() + INTERVAL 1 DAY → NOW() + INTERVAL '1 day'
UNSIGNED → 不支持,用 CHECK (col >= 0)
pgloader(一键迁移)。
max_locks_per_transaction:默认 64,设为 256 或更高
max_pred_locks_per_transaction
DELETE + VACUUM FULL
SELECT pg_database.datname,
pg_size_pretty(pg_database_size(pg_database.datname))
FROM pg_database ORDER BY pg_database_size(pg_database.datname) DESC;
-- 查看最大的表
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;
statement_timeout = 0(备份会话中)
pg_dump -Fd -j 4 -f /backup/mydb mydb
pg_basebackup 物理备份
pg_dump -Fc -Z 6 mydb > mydb.dump
DROP DATABASE mydb; CREATE DATABASE mydb;
--clean 参数:pg_restore --clean -d mydb backup.dump
--if-exists 避免删除不存在的对象报错:pg_restore --clean --if-exists -d mydb backup.dump
-- 主库上查看
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication;
-- 备库上查看
SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;
max_standby_streaming_delay,关闭备库的热备查询(或设置查询超时)
SELECT pg_create_physical_replication_slot('standby_slot');
SELECT * FROM pg_replication_slots;
primary_conninfo 中移除 slot_name 配置
SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes FROM pg_replication_slots;# 提升备库为主库 pg_ctl promote -D /var/lib/postgresql/17/main # 或 SELECT pg_promote();提升后需要做的工作:
pg_rewind 或重新做 basebackup 加入集群
# 使用 pg_rewind 让旧主库跟随新主库 pg_rewind --target-pgdata=/var/lib/postgresql/17/main \ --source-server="host=new_primary port=5432 user=replicator"
ALTER SUBSCRIPTION my_sub SKIP (lsn = 'X/XXXXXXXX');
-- 创建订阅时设置 CREATE SUBSCRIPTION my_sub CONNECTION 'host=primary dbname=mydb' PUBLICATION my_pub WITH (origin = none);
SELECT slot_name, active FROM pg_replication_slots; → 删除无用槽:SELECT pg_drop_replication_slot('unused_slot');
archive_command 是否正常执行
SELECT pid, xact_start, query FROM pg_stat_activity WHERE xact_start < NOW() - INTERVAL '1 hour';
wal_keep_size(PostgreSQL 13+)或 wal_keep_segments(旧版本)。
-- 授予权限 GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myuser; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO myuser; -- 设置默认权限(对将来创建的表也生效) ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO myuser; -- 查看当前权限 \dp users
-- 启用 RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- 创建策略:用户只能看到自己的订单
CREATE POLICY user_orders ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::bigint);
-- 在应用连接时设置用户 ID
SET app.current_user_id = '123';
SELECT * FROM orders; -- 只返回 user_id=123 的订单
-- 超级用户不受 RLS 限制
-- 要对表所有者也启用 RLS:
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
# Python (psycopg2)
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# Node.js (pg)
client.query('SELECT * FROM users WHERE id = $1', [userId])
# Java (JDBC)
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);
quote_ident() 和 quote_literal() 转义动态标识符。
# 安装 sudo apt install postgresql-17-pgaudit # postgresql.conf shared_preload_libraries = 'pgaudit' pgaudit.log = 'write, ddl' pgaudit.log_catalog = off方案二:触发器实现审计
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
table_name TEXT, action TEXT,
old_data JSONB, new_data JSONB,
changed_by TEXT DEFAULT current_user,
changed_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log(table_name, action, old_data, new_data)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_users AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger();
-- 安装扩展 CREATE EXTENSION pg_stat_statements; CREATE EXTENSION pgcrypto; CREATE EXTENSION "uuid-ossp"; -- 查看已安装扩展 SELECT * FROM pg_extension; -- 查看可用扩展 SELECT * FROM pg_available_extensions WHERE name LIKE 'pg%';