PostgreSQL 15于2022年10月发布,带来了很多新特性。
本文分享PostgreSQL 15的进阶技巧,包括性能优化、索引技巧、查询优化、新特性使用、运维技巧等,帮你更好地使用PostgreSQL。
一、PostgreSQL 15新特性回顾
先简单回顾一下PostgreSQL 15的重要新特性。
1. 合并行(MERGE)
PostgreSQL 15终于支持了标准SQL的MERGE语句。
MERGE INTO target_table t
USING source_table s
ON t.id = s.id
WHEN MATCHED THEN
UPDATE SET name = s.name
WHEN NOT MATCHED THEN
INSERT (id, name) VALUES (s.id, s.name);MERGE可以在一条语句中完成插入和更新,比之前的INSERT ... ON CONFLICT更标准、更灵活。
2. 行级权限(RLS)增强
行级安全策略更强大了。
- 支持更复杂的策略表达式
- 性能优化
- 更好的错误提示
3. 逻辑复制增强
- 支持表的列过滤
- 支持行过滤(WHERE子句)
- 支持两阶段提交
- 更灵活的复制配置
4. 性能提升
- 排序性能提升
- 并行查询优化
- 索引扫描优化
- 内存管理改进
5. 其他改进
- JSON功能增强
- 分区表改进
- 监控视图增强
- 安全性提升
二、性能优化技巧
1. 合理使用索引
索引是性能优化的关键,但不是越多越好。
创建索引:
-- 普通索引
CREATE INDEX idx_users_name ON users(name);
-- 唯一索引
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- 复合索引
CREATE INDEX idx_users_age_name ON users(age, name);
-- 部分索引(只索引满足条件的行)
CREATE INDEX idx_users_active ON users(name) WHERE active = true;
-- 表达式索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));
-- GIN索引(适合数组、JSON、全文搜索)
CREATE INDEX idx_users_tags ON users USING GIN(tags);索引使用技巧:
- 复合索引的列顺序很重要,把等值查询的列放前面
- 不要在低选择性的列上建索引(如性别)
- 定期检查无用索引,删除不用的
- 索引不是越多越好,会影响写入性能
检查索引使用情况:
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;2. 查询优化
用EXPLAIN分析查询:
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 30 ORDER BY name;看执行计划,注意:
- Seq Scan:全表扫描,数据量大时要避免
- Index Scan:索引扫描,好
- Bitmap Heap Scan:位图扫描,也不错
- Nested Loop:嵌套循环,小表用可以
- Hash Join:哈希连接,大表用更好
- Sort:排序,数据量大时慢
常见的查询优化:
- 避免SELECT *,只查需要的列
- 用LIMIT限制返回行数
- 避免在WHERE子句中对列用函数(会导致索引失效)
- 用UNION ALL代替UNION(如果不需要去重)
- 大表分页用游标或keyset分页,不要用OFFSET
不好的写法:
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
-- 索引在email上,但用了LOWER函数,索引失效好的写法:
SELECT * FROM users WHERE email ILIKE 'test@example.com';
-- 或建表达式索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';3. 分页优化
深分页(OFFSET很大)会很慢。
-- 慢:OFFSET 100000 需要扫描100000行
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 100000;
-- 快:用keyset分页
SELECT * FROM users WHERE id > 100000 ORDER BY id LIMIT 20;keyset分页利用索引,速度快很多。
4. 批量操作
批量操作比单条操作快很多。
-- 批量插入
INSERT INTO users (name, email) VALUES
('张三', 'zhang@example.com'),
('李四', 'li@example.com'),
('王五', 'wang@example.com');
-- 批量更新
UPDATE users SET status = 'active' WHERE id IN (1, 2, 3);
-- 批量删除
DELETE FROM users WHERE id IN (1, 2, 3);如果数据量特别大,可以用COPY命令。
三、高级数据类型技巧
1. JSON/JSONB
PostgreSQL对JSON支持很好,JSONB是二进制格式,支持索引。
-- 创建JSONB列
CREATE TABLE products (
id serial PRIMARY KEY,
name text,
attributes jsonb
);
-- 插入
INSERT INTO products (name, attributes) VALUES
('手机', '{"color": "black", "storage": 128, "brand": "Apple"}');
-- 查询JSON字段
SELECT name, attributes->>'color' as color
FROM products
WHERE attributes @> '{"brand": "Apple"}';
-- JSONB索引
CREATE INDEX idx_products_attributes ON products USING GIN(attributes);
-- 特定路径的索引
CREATE INDEX idx_products_brand ON products ((attributes->>'brand'));JSONB的操作符:
- ->:获取JSON对象字段,返回JSON
- ->>:获取JSON对象字段,返回文本
- @>:包含
- ?:键存在
- ||:合并
2. 数组
PostgreSQL支持数组类型。
-- 创建数组列
CREATE TABLE posts (
id serial PRIMARY KEY,
title text,
tags text[]
);
-- 插入
INSERT INTO posts (title, tags) VALUES
('PostgreSQL入门', ARRAY['数据库', '教程', 'PostgreSQL']);
-- 查询(包含某个标签)
SELECT * FROM posts WHERE tags @> ARRAY['数据库'];
-- 数组索引
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);
-- 数组函数
SELECT
array_length(tags, 1) as tag_count,
array_to_string(tags, ', ') as tags_str
FROM posts;3. 范围类型
范围类型适合表示时间段、数值范围等。
-- 创建范围列
CREATE TABLE reservations (
id serial PRIMARY KEY,
room_id int,
during tsrange -- 时间范围
);
-- 插入
INSERT INTO reservations (room_id, during) VALUES
(1, '[2022-11-01 09:00, 2022-11-01 12:00)');
-- 查询重叠的范围
SELECT * FROM reservations
WHERE during && '[2022-11-01 10:00, 2022-11-01 11:00)';
-- 范围索引
CREATE INDEX idx_reservations_during ON reservations USING GIST(during);四、分区表技巧
PostgreSQL支持表分区,大表用分区可以提升性能。
1. 创建分区表
-- 创建主表
CREATE TABLE orders (
id serial,
user_id int,
amount numeric,
created_at timestamp
) PARTITION BY RANGE (created_at);
-- 创建分区(按月)
CREATE TABLE orders_2022_11 PARTITION OF orders
FOR VALUES FROM ('2022-11-01') TO ('2022-12-01');
CREATE TABLE orders_2022_12 PARTITION OF orders
FOR VALUES FROM ('2022-12-01') TO ('2023-01-01');2. 分区的好处
- 查询只扫描相关分区,速度快
- 可以快速删除旧数据(DROP分区)
- 可以把旧分区移到慢存储
- 索引更小,更高效
3. 分区注意事项
- 分区键要选好,通常是时间或ID
- 分区不要太多也不要太少
- 主键必须包含分区键
- 外键对分区表支持有限
五、事务和并发技巧
1. 事务隔离级别
PostgreSQL支持四种隔离级别:
- READ UNCOMMITTED(实际和READ COMMITTED一样)
- READ COMMITTED(默认)
- REPEATABLE READ
- SERIALIZABLE
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 你的操作
COMMIT;根据业务需求选择隔离级别,SERIALIZABLE最安全但性能最低。
2. 乐观锁
用版本号实现乐观锁。
-- 表加version字段
ALTER TABLE users ADD COLUMN version int DEFAULT 0;
-- 更新时检查版本
UPDATE users
SET name = '新名字', version = version + 1
WHERE id = 1 AND version = 5;
-- 如果影响行数为0,说明被别人修改了3. 悲观锁
用SELECT ... FOR UPDATE实现悲观锁。
BEGIN;
-- 锁定行
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 修改
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;注意:FOR UPDATE会阻塞其他事务,要尽快提交。
4. 避免死锁
死锁的常见原因:
- 多个事务以不同顺序锁定资源
- 长事务持有锁太久
避免方法:
- 统一锁定顺序
- 事务尽量短
- 设置锁超时
- 用乐观锁代替悲观锁
六、运维技巧
1. 监控
PostgreSQL有很多监控视图。
-- 活动连接
SELECT * FROM pg_stat_activity;
-- 慢查询(需要开启pg_stat_statements)
SELECT
query,
calls,
total_exec_time,
mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- 表大小
SELECT
relname,
pg_size_pretty(pg_total_relation_size(relid)) as size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
-- 索引大小
SELECT
indexrelname,
pg_size_pretty(pg_relation_size(indexrelid)) as size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;2. VACUUM和ANALYZE
PostgreSQL用MVCC,需要定期VACUUM。
-- 手动VACUUM
VACUUM ANALYZE users;
-- 全量VACUUM(会锁表,谨慎使用)
VACUUM FULL users;
-- 只分析统计信息
ANALYZE users;注意:
- autovacuum默认开启,一般不用手动VACUUM
- VACUUM FULL会锁表,不要在高峰期用
- 大表可以用pg_repack在线重建
3. 备份
# 逻辑备份
pg_dump -U postgres dbname > backup.sql
# 恢复
psql -U postgres dbname < backup.sql
# 物理备份(PITR)
# 配置archive_mode和archive_command
# 用pg_basebackup做基础备份重要的数据库,一定要有备份策略,定期测试恢复。
4. 连接池
PostgreSQL的连接是进程,连接数不能太多。
用连接池:
- PgBouncer:轻量级连接池
- 应用层连接池(如HikariCP)
# PgBouncer配置示例
[databases]
mydb = host=localhost port=5432 dbname=mydb
[pgbouncer]
listen_port = 6432
max_client_conn = 1000
default_pool_size = 20七、实用技巧
1. 生成测试数据
-- 生成100万条测试数据
INSERT INTO users (name, email, age)
SELECT
'user' || i,
'user' || i || '@example.com',
floor(random() * 80 + 18)::int
FROM generate_series(1, 1000000) as i;2. 查看表结构
-- 查看表结构
\d users
-- 查看表的详细信息
\d+ users
-- 查看所有表
\dt
-- 查看索引
\di3. 时间处理
-- 当前时间
SELECT NOW();
SELECT CURRENT_TIMESTAMP;
-- 时间加减
SELECT NOW() - INTERVAL '1 day';
SELECT NOW() + INTERVAL '1 month';
-- 格式化
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS');
-- 时区转换
SELECT NOW() AT TIME ZONE 'UTC';4. 窗口函数
窗口函数很强大,可以做复杂分析。
-- 每组前N名
SELECT * FROM (
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rn
FROM employees
) t WHERE rn <= 3;
-- 累计求和
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) as cumulative
FROM daily_sales;
-- 移动平均
SELECT
date,
amount,
AVG(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as avg_7d
FROM daily_sales;八、常见坑
1. 字符集问题
- 数据库创建时指定UTF8
- 客户端和服务端字符集一致
- 避免用SQL_ASCII
CREATE DATABASE mydb
ENCODING 'UTF8'
LC_COLLATE 'en_US.UTF8'
LC_CTYPE 'en_US.UTF8'
TEMPLATE template0;2. 时区问题
- 用timestamptz代替timestamp
- 统一用UTC存储
- 显示时再转换时区
3. 自增ID问题
- 用serial或identity列
- 分布式系统考虑用UUID或雪花ID
- 自增ID不连续是正常的(事务回滚会消耗ID)
4. 空字符串和NULL
- 空字符串''和NULL是不同的
- 查询时注意区分
- 用COALESCE处理NULL
SELECT COALESCE(name, '未知') FROM users;九、写在最后
PostgreSQL是一个强大的数据库,15版本带来了很多改进。
掌握这些进阶技巧,可以让你更好地使用PostgreSQL:
- 合理使用索引,优化查询
- 用好高级数据类型(JSONB、数组、范围)
- 大表用分区
- 正确处理事务和并发
- 做好监控和运维
PostgreSQL的功能很多,本文只是冰山一角。建议多实践,多阅读官方文档,不断积累经验。
最后,用一句话总结:"PostgreSQL很强大,用好它需要不断学习和实践。掌握进阶技巧,让你的数据库又快又稳。"
愿你用好PostgreSQL,构建高性能的应用。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录