15人参与 • 2026-01-27 • MsSqlserver
在 postgresql 中,索引是提升查询性能最有效的手段之一。然而,“盲目建索引”不仅无法提升性能,反而会拖慢写入速度、浪费存储空间、增加维护成本。优秀的索引设计需要结合数据分布、查询模式、业务场景进行系统性思考。
本文将从 索引类型选择、列顺序设计、复合索引策略、部分索引应用、统计信息管理、反模式识别 六大维度,深入剖析 postgresql 索引设计的核心原则,并提供可落地的最佳实践。
适用场景:
=)>, <, between)order by)like 'abc%')内部结构:
创建语法:
create index idx_orders_user_id on orders(user_id);
注意:postgresql 的 b-tree 索引默认不存储 null 值(但可通过
is not null条件使用部分索引覆盖)。
适用场景:
=)优势:
局限:
order bydistinct 优化创建语法:
create index idx_users_email_hash on users using hash(email);
建议:除非明确测试证明 hash 更优,否则优先使用 b-tree。
适用场景:
array @> array[1])data @> '{"key": "value"}')tsvector @@ tsquery)pg_trgm 模糊匹配(name like '%alice%')特点:
创建示例:
-- jsonb 索引
create index idx_products_attrs_gin on products using gin(attributes);
-- 全文检索
create index idx_articles_fts on articles using gin(to_tsvector('english', content));
-- 模糊搜索(需 pg_trgm 扩展)
create extension if not exists pg_trgm;
create index idx_users_name_trgm on users using gin(name gin_trgm_ops);适用场景:
ltree(树形路径)与 gin 对比:
创建示例:
-- 全文检索(gist 版本)
create index idx_articles_fts_gist on articles using gist(to_tsvector('english', content));
-- ltree 路径索引
create index idx_categories_path on categories using gist(path);适用场景:
created_at > '2026-01-01')原理:
优势:
创建示例:
-- 时间序列表 create index idx_logs_created_brin on logs using brin(created_at);
建议:日志、监控、iot 数据等场景首选 brin。
索引不是为表设计的,而是为查询语句设计的。
通过以下方式识别关键查询:
pg_stat_statements 扩展-- 启用 pg_stat_statements create extension pg_stat_statements; -- 查看最耗时的查询 select query, calls, total_exec_time, rows from pg_stat_statements order by total_exec_time desc limit 10;
原则:索引应覆盖 where 子句中的过滤条件。
-- 查询:select * from orders where user_id = 123 and status = 'paid'; -- 推荐索引: create index idx_orders_user_status on orders(user_id, status);
注意:若
status只有少数几个值(如 ‘paid’, ‘pending’),将其放在复合索引第二位可提升选择性。
问题:索引扫描后仍需回表(heap fetch)获取其他列,增加 i/o。
解决方案:使用 include 子句(postgresql 11+)将非过滤列加入索引。
-- 查询:select order_id, total from orders where user_id = 123; -- 普通索引: create index idx_orders_user_id on orders(user_id); -- 需回表取 total -- 覆盖索引: create index idx_orders_user_id_covering on orders(user_id) include (total); -- 执行计划:index only scan(无需回表)
优势:
- 减少 i/o
- 提升缓存命中率
- 适用于只读或低频更新列
复合索引的性能高度依赖列的顺序。遵循 “等值列在前,范围列在后” 原则。
b-tree 复合索引 (a, b, c) 可用于:
where a = ?where a = ? and b = ?where a = ? and b = ? and c = ?where a = ? and b = ? order by c但不能用于:
where b = ?where c = ?where b = ? and c = ?查询条件中有哪些列? ├─ 全是等值(=) → 任意顺序(建议高选择性列在前) ├─ 含范围(>, <, between) → 等值列在前,范围列在后 └─ 含排序(order by) → 将排序列放在最后(若前面是等值)
-- 查询:where user_id = 123 and created_at > '2026-01-01' -- 正确顺序:(user_id, created_at) create index idx_orders_user_created on orders(user_id, created_at); -- 错误顺序:(created_at, user_id) → created_at 范围扫描后仍需过滤 user_id
-- 查询:where status = 'paid' order by created_at desc limit 10 -- 推荐索引:(status, created_at desc) create index idx_orders_status_created on orders(status, created_at desc); -- 可实现 index scan + limit,避免 sort
注意:postgresql 11+ 支持
nulls first/last和降序索引,可精确匹配order by。
当查询只关注数据子集时,部分索引可大幅减小索引体积并提升效率。
status = 'active')created_at > current_date - interval '30 days')email is not null)-- 场景:90% 的订单是 'completed',但常查 'pending' create index idx_orders_pending on orders(user_id) where status = 'pending'; -- 查询必须包含相同条件才能使用索引 select * from orders where user_id = 123 and status = 'pending';
警告:查询条件必须完全匹配部分索引的
where子句,否则无法使用。
每个索引都有代价:
-- 查看从未使用的索引
select schemaname, tablename, indexname, idx_scan
from pg_stat_user_indexes
where idx_scan = 0
order by schemaname, tablename;
-- 查看低效索引(扫描次数远低于表大小)
select
schemaname,
tablename,
indexname,
idx_scan,
pg_size_pretty(pg_relation_size(indexname::regclass)) as index_size
from pg_stat_user_indexes
where idx_scan < 100 -- 阈值根据业务调整
order by pg_relation_size(indexname::regclass) desc;常见冗余情况:
建议:定期审计索引使用情况,删除无用索引。
索引能否被使用,最终由查询优化器决定。而优化器依赖统计信息和成本参数。
-- 手动更新统计信息(大批量导入后执行)
analyze table_name;
-- 调整自动分析阈值
alter table orders set (
autovacuum_analyze_scale_factor = 0.05, -- 默认 0.1
autovacuum_analyze_threshold = 500 -- 默认 50
);-- ssd 随机读接近顺序读,降低 random_page_cost set random_page_cost = 1.1; -- 默认 4.0(机械盘) -- 若内存充足,可降低 cpu_tuple_cost set cpu_tuple_cost = 0.005; -- 默认 0.01
建议:在 ssd 服务器上,将
random_page_cost设为 1.1~1.3。
-- 性别只有 'm'/'f',索引几乎无效 create index idx_users_gender on users(gender);
判断标准:n_distinct / 表行数 < 0.01(即唯一值占比 < 1%)
orders.user_id 常用于查询,应建索引;但 order_items.order_id 作为主表关联,若不单独查询,可不建is null,需单独建部分索引:create index idx_users_phone_null on users((1)) where phone is null;
-- 索引
create index idx_users_upper_email on users(upper(email));
-- 查询必须完全一致
select * from users where upper(email) = 'alice@example.com'; -- ✅
select * from users where upper(email) = lower('alice@example.com'); -- ❌ 不匹配有时,改写查询比建索引更有效。
-- 原查询(可能无法使用索引) select * from users where email = 'a' or name = 'alice'; -- 优化后(每个分支独立使用索引) select * from users where email = 'a' union select * from users where name = 'alice';
-- 原查询 select * from logs where date(created_at) = '2026-01-25'; -- 优化后(使用范围) select * from logs where created_at >= '2026-01-25' and created_at < '2026-01-26';
-- 原查询(需回表) select user_id, count(*) from orders group by user_id; -- 若 orders 表很大,可建覆盖索引 create index idx_orders_user_covering on orders(user_id) include (order_id); -- 执行计划:index only scan + groupaggregate
在创建索引前,问自己以下问题:
遵循这些原则,你将能设计出高效、精简、可维护的索引体系,在查询性能与写入成本之间取得最佳平衡。记住:好的索引不是越多越好,而是恰到好处。
到此这篇关于postgresql索引的设计原则和最佳实践的文章就介绍到这了,更多相关postgresql索引设计原则内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论