Skip to content

PostgreSQL 索引创建策略

索引类型选择

1. B树索引

B树索引是PostgreSQL中最常用的索引类型,适合大多数查询场景:

sql
-- 创建基本B树索引
CREATE INDEX idx_员工表_部门ID ON 员工表(部门ID);

-- 创建唯一B树索引
CREATE UNIQUE INDEX idx_员工表_邮箱 ON 员工表(邮箱);

-- 创建复合B树索引
CREATE INDEX idx_员工表_部门状态 ON 员工表(部门ID, 状态);

2. 哈希索引

哈希索引适合等值查询,但不支持范围查询:

sql
-- 创建哈希索引
CREATE INDEX idx_员工表_身份证号 ON 员工表 USING HASH(身份证号);

3. GIN 索引

GIN(Generalized Inverted Index)索引适合数组、JSON、全文搜索等复杂类型:

sql
-- 创建GIN索引用于数组类型
CREATE INDEX idx_产品表_标签 ON 产品表 USING GIN(标签);

-- 创建GIN索引用于JSONB类型
CREATE INDEX idx_订单表_扩展信息 ON 订单表 USING GIN(扩展信息);

-- 创建GIN索引用于全文搜索
CREATE INDEX idx_文章表_内容 ON 文章表 USING GIN(to_tsvector('chinese', 内容));

4. GiST 索引

GiST(Generalized Search Tree)索引适合空间数据、范围类型等:

sql
-- 创建GiST索引用于范围类型
CREATE INDEX idx_预订表_时间范围 ON 预订表 USING GiST(时间范围);

-- 创建GiST索引用于空间数据
CREATE INDEX idx_地理位置表_坐标 ON 地理位置表 USING GiST(坐标);

索引创建原则

1. 选择合适的列

  • 选择查询频率高的列
  • 选择选择性高的列(不同值比例高)
  • 选择用于JOIN、WHERE、ORDER BY、GROUP BY的列
  • 避免在低选择性列上创建索引(如性别、状态等)

2. 复合索引设计

  • 将选择性高的列放在前面
  • 考虑查询的排序需求
  • 避免创建过多的复合索引
  • 复合索引的前缀匹配原则
sql
-- 复合索引示例:选择性高的列在前
CREATE INDEX idx_订单表_客户日期 ON 订单表(客户ID, 订单日期);

-- 可以支持以下查询
SELECT * FROM 订单表 WHERE 客户ID = 123;
SELECT * FROM 订单表 WHERE 客户ID = 123 AND 订单日期 >= '2023-01-01';
SELECT * FROM 订单表 WHERE 客户ID = 123 ORDER BY 订单日期;

3. 避免过度索引

  • 索引会增加写操作开销
  • 索引会占用存储空间
  • 索引会增加查询优化器的选择难度
  • 定期清理无用索引

高级索引技术

1. 部分索引

只对表中部分数据创建索引,适合查询特定条件的数据:

sql
-- 为活跃客户创建部分索引
CREATE INDEX idx_客户表_活跃 ON 客户表(最后活跃日期)
WHERE 状态 = '活跃';

-- 为高价值订单创建部分索引
CREATE INDEX idx_订单表_高价值 ON 订单表(客户ID, 订单金额)
WHERE 订单金额 > 10000;

2. 函数索引

对列的函数结果创建索引,适合需要函数计算的查询:

sql
-- 创建函数索引用于大写查询
CREATE INDEX idx_员工表_姓名大写 ON 员工表(UPPER(姓名));

-- 查询时可以使用该索引
SELECT * FROM 员工表 WHERE UPPER(姓名) = '张三';

-- 创建函数索引用于日期查询
CREATE INDEX idx_订单表_月份 ON 订单表(EXTRACT(MONTH FROM 订单日期));

3. 覆盖索引

包含查询所需的所有列,避免回表查询:

sql
-- 创建覆盖索引,包含常用查询列
CREATE INDEX idx_订单表_覆盖 ON 订单表(客户ID) INCLUDE (订单日期, 订单金额, 状态);

-- 查询时直接使用覆盖索引,无需回表
SELECT 订单日期, 订单金额, 状态 FROM 订单表 WHERE 客户ID = 123;

索引创建最佳实践

1. 生产环境创建索引

sql
-- 生产环境创建索引,避免阻塞写操作
CREATE INDEX CONCURRENTLY idx_表名_列名 ON 表名(列名);

-- 查看索引创建进度
SELECT 
    pid,
    query,
    state,
    wait_event,
    wait_event_type
FROM pg_stat_activity
WHERE query LIKE '%CREATE INDEX%';

2. 索引命名规范

  • 使用 idx_表名_列名 格式
  • 复合索引使用 idx_表名_列名1_列名2 格式
  • 部分索引使用 idx_表名_列名_条件 格式
  • 函数索引使用 idx_表名_函数名_列名 格式

3. 索引维护

sql
-- 查看索引大小
SELECT 
    schemaname,
    tablename,
    indexname,
    pg_size_pretty(pg_relation_size(indexname)) AS 索引大小
FROM pg_indexes
WHERE schemaname = 'public';

-- 查看索引使用情况
SELECT 
    relname AS 表名,
    indexrelname AS 索引名,
    idx_scan AS 扫描次数,
    idx_tup_read AS 索引读取行数,
    idx_tup_fetch AS 表读取行数
FROM pg_stat_user_indexes u
JOIN pg_stat_user_tables t ON u.relid = t.relid;

-- 重建索引
REINDEX INDEX CONCURRENTLY idx_表名_列名;

-- 删除无用索引
DROP INDEX IF EXISTS idx_无用索引;

索引创建的性能影响

1. 对写操作的影响

  • INSERT:需要维护索引
  • UPDATE:如果更新了索引列,需要维护索引
  • DELETE:需要维护索引

2. 对读操作的影响

  • 提高查询速度
  • 占用缓存空间
  • 增加查询优化时间

3. 平衡读写性能

  • 对于写密集型表,减少索引数量
  • 对于读密集型表,适当增加索引
  • 定期分析表统计信息
sql
-- 分析表统计信息
ANALYZE 表名;

-- 自动分析配置
ALTER SYSTEM SET autovacuum_analyze_scale_factor = 0.05;
ALTER SYSTEM SET autovacuum_analyze_threshold = 50;

常见问题(FAQ)

Q1:如何判断是否需要创建索引?

A1:判断是否需要创建索引的方法:

  1. 分析查询执行计划,查看是否有全表扫描
  2. 检查查询的WHERE、JOIN、ORDER BY、GROUP BY子句
  3. 考虑列的选择性和查询频率
  4. 测试创建索引前后的查询性能

Q2:复合索引和多个单列索引有什么区别?

A2:复合索引和多个单列索引的区别:

特性复合索引多个单列索引
存储存储在一个索引结构中存储在多个索引结构中
前缀匹配支持前缀匹配不支持
查询优化优化器更容易选择优化器选择难度大
维护成本较低较高
覆盖查询支持不支持

Q3:什么时候应该使用部分索引?

A3:适合使用部分索引的场景:

  1. 表中只有部分数据被频繁查询
  2. 查询条件包含固定值
  3. 索引列选择性在特定条件下很高
  4. 希望减少索引大小和维护成本

Q4:如何选择索引类型?

A4:选择索引类型的依据:

查询类型推荐索引类型
等值查询B树、哈希
范围查询B树、GiST
数组查询GIN
JSON查询GIN
全文搜索GIN、GiST
空间查询GiST、SP-GiST
范围类型GiST

Q5:如何监控索引性能?

A5:监控索引性能的方法:

  1. 使用 pg_stat_user_indexes 查看索引使用情况
  2. 使用 EXPLAIN ANALYZE 分析查询计划
  3. 监控索引大小和增长趋势
  4. 检查长时间运行的索引维护操作
  5. 使用 pg_stat_statements 查看索引相关查询的性能

Q6:如何处理大型表的索引创建?

A6:大型表索引创建的最佳实践:

  1. 使用 CONCURRENTLY 选项避免阻塞
  2. 在业务低峰期执行
  3. 考虑使用分区表减少索引大小
  4. 分批创建索引
  5. 监控索引创建进度

Q7:索引可以提高所有查询的性能吗?

A7:不是所有查询都能通过索引提高性能:

  1. 全表扫描可能比索引扫描更快(当查询返回大部分数据时)
  2. 低选择性列上的索引可能不会被使用
  3. 频繁更新的列上的索引可能影响写性能
  4. 复杂查询可能无法有效使用索引

Q8:如何清理无用索引?

A8:清理无用索引的步骤:

  1. 分析索引使用情况,找出长时间未使用的索引
  2. 测试删除索引前后的查询性能
  3. 使用 DROP INDEX CONCURRENTLY 删除无用索引
  4. 定期审查索引使用情况
  5. 建立索引创建和删除的审批流程