PostgreSQL教程FG310-PostgreSQL索引优化
本文档风哥主要介绍PostgreSQL索引优化,包括索引类型、索引创建、索引维护等内容。风哥教程参考PostgreSQL官方文档Server Administration部分,适合DBA人员在生产环境中使用。
Part01-基础概念与理论知识
1.1 PostgreSQL索引概述
PostgreSQL索引是一种数据结构,用于提高数据库查询的性能。索引可以帮助数据库系统快速定位数据,减少数据扫描的范围,从而提高查询速度。
- 提高查询速度:减少数据扫描的范围
- 减少系统资源消耗:降低CPU、内存和磁盘I/O使用
- 提高系统吞吐量:处理更多并发查询
- 优化数据库性能:提高整体系统性能
1.2 PostgreSQL索引类型
PostgreSQL支持多种类型的索引,每种索引适用于不同的场景:
- B-tree索引:最常用的索引类型,适用于等值查询和范围查询
- Hash索引:适用于等值查询,不支持范围查询
- GiST索引:适用于几何数据和全文搜索
- SP-GiST索引:适用于非平衡数据结构
- GIN索引:适用于数组、JSON等复合数据类型
- BRIN索引:适用于大型表的范围查询
- Partial索引:只包含满足特定条件的行
- Expression索引:基于表达式的索引
1.3 PostgreSQL索引结构
PostgreSQL索引的结构主要包括以下几个部分:
– 根节点:存储索引的顶层节点
– 分支节点:存储索引的中间节点
– 叶节点:存储索引的叶子节点,包含索引键和行指针
# 索引查找过程
1. 从根节点开始,根据索引键的值确定下一层节点
2. 递归遍历分支节点,直到到达叶节点
3. 在叶节点中查找匹配的索引键
4. 根据行指针找到对应的表行
# 索引大小和维护
– 索引大小:取决于索引键的大小和数据量
– 索引维护:插入、更新和删除操作会导致索引的维护
– 索引碎片:频繁的更新操作会导致索引碎片
Part02-生产环境规划与建议
2.1 索引规划
在生产环境中,合理的索引规划是确保索引有效性的关键:
1. 明确查询模式:了解系统的查询类型和频率
2. 识别频繁查询的列:找出经常用于查询条件的列
3. 评估数据分布:了解列的数据分布情况
4. 考虑索引成本:权衡索引的维护成本和查询收益
5. 制定索引策略:根据查询模式和数据分布制定索引策略
6. 定期评估:定期评估索引的有效性,调整索引策略
# 索引规划示例
– 高频查询列:id, email, created_at
– 复合查询列:user_id, created_at
– 范围查询列:created_at, amount
– 唯一约束列:email, username
2.2 索引需求分析
索引需求分析应包括以下内容:
- 查询模式:分析系统的查询类型和频率
- 数据量:评估表的数据量大小和增长趋势
- 数据分布:了解列的数据分布情况
- 性能要求:确定查询的性能要求
- 维护成本:评估索引的维护成本
2.3 索引设计策略
索引设计策略应考虑以下因素:
1. 选择合适的索引类型:根据查询模式选择合适的索引类型
2. 优化索引键顺序:在复合索引中,将最常用的列放在前面
3. 避免过度索引:不要创建过多的索引,会增加维护成本
4. 考虑部分索引:对于特定条件的查询,使用部分索引
5. 考虑表达式索引:对于基于表达式的查询,使用表达式索引
6. 定期维护索引:定期重建或重新索引,减少索引碎片
# 索引设计示例
– 等值查询:B-tree索引
– 范围查询:B-tree索引
– 几何数据:GiST索引
– 数组和JSON:GIN索引
– 大型表范围查询:BRIN索引
– 特定条件查询:Partial索引
– 基于表达式的查询:Expression索引
Part03-生产环境项目实施方案
3.1 索引创建
3.1.1 创建B-tree索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_email_idx ON fgedu_users(email);”
# 创建唯一索引
$ psql -U fgedu -d fgedudb -c “CREATE UNIQUE INDEX fgedu_users_email_idx ON fgedu_users(email);”
# 创建复合索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_name_email_idx ON fgedu_users(name, email);”
# 创建部分索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_active_idx ON fgedu_users(id) WHERE active = true;”
# 创建表达式索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_lower_email_idx ON fgedu_users(LOWER(email));”
# 验证索引创建
$ psql -U fgedu -d fgedudb -c “\d fgedu_users”
# 查看索引信息
$ psql -U fgedu -d fgedudb -c “SELECT * FROM pg_indexes WHERE tablename = ‘fgedu_users’;”
3.1.2 创建其他类型的索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_tags_idx ON fgedu_users USING GIN(tags);”
# 创建GiST索引(适用于几何数据)
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_locations_point_idx ON fgedu_locations USING GiST(point);”
# 创建BRIN索引(适用于大型表)
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_logs_created_at_idx ON fgedu_logs USING BRIN(created_at);”
# 创建Hash索引(适用于等值查询)
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_users_id_hash_idx ON fgedu_users USING HASH(id);”
3.2 索引维护
3.2.1 索引重建
$ psql -U fgedu -d fgedudb -c “REINDEX INDEX fgedu_users_email_idx;”
# 重建表的所有索引
$ psql -U fgedu -d fgedudb -c “REINDEX TABLE fgedu_users;”
# 重建数据库的所有索引
$ psql -U fgedu -d fgedudb -c “REINDEX DATABASE fgedudb;”
# 重建系统表的索引
$ psql -U fgedu -d fgedudb -c “REINDEX SYSTEM fgedudb;”
# 验证索引
$ psql -U fgedu -d fgedudb -c “SELECT * FROM pg_stat_user_indexes WHERE schemaname = ‘public’;”
3.2.2 索引碎片清理
$ psql -U fgedu -d fgedudb -c “SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
CASE WHEN pg_total_relation_size(c.oid) > 0 THEN
round(100 * pg_indexes_size(c.oid) / pg_total_relation_size(c.oid), 2)
ELSE 0 END AS index_ratio
FROM
pg_class c
JOIN
pg_index i ON c.oid = i.indrelid
JOIN
pg_indexes ix ON c.relname = ix.tablename AND i.indexrelid::regclass::text = ix.indexname
WHERE
c.relkind = ‘r’ AND
schemaname = ‘public’
ORDER BY
pg_indexes_size(c.oid) DESC;
”
# 清理索引碎片
$ psql -U fgedu -d fgedudb -c “VACUUM ANALYZE fgedu_users;”
# 重建碎片化严重的索引
$ psql -U fgedu -d fgedudb -c “REINDEX INDEX fgedu_users_email_idx;”
3.3 索引使用
3.3.1 查看索引使用情况
$ psql -U fgedu -d fgedudb -c “SELECT
schemaname,
tablename,
indexname,
idx_scan AS index_scans,
idx_tup_read AS tuples_read,
idx_tup_fetch AS tuples_fetched
FROM
pg_stat_user_indexes
JOIN
pg_stat_user_tables ON pg_stat_user_indexes.schemaname = pg_stat_user_tables.schemaname AND pg_stat_user_indexes.tablename = pg_stat_user_tables.tablename
WHERE
pg_stat_user_indexes.schemaname = ‘public’
ORDER BY
idx_scan DESC;
”
# 查看未使用的索引
$ psql -U fgedu -d fgedudb -c “SELECT
schemaname,
tablename,
indexname
FROM
pg_stat_user_indexes
WHERE
schemaname = ‘public’ AND
idx_scan = 0
ORDER BY
tablename, indexname;
”
# 查看索引扫描统计
$ psql -U fgedu -d fgedudb -c “EXPLAIN ANALYZE SELECT * FROM fgedu_users WHERE email = ‘test@fgedu.net.cn’;”
Part04-生产案例与实战讲解
4.1 索引创建案例
4.1.1 生产环境索引创建
# 1. 分析查询模式
$ psql -U fgedu -d fgedudb -c “SELECT
query,
calls,
total_exec_time,
mean_exec_time
FROM
pg_stat_statements
ORDER BY
total_exec_time DESC
LIMIT 10;
”
# 2. 识别频繁查询的列
$ psql -U fgedu -d fgedudb -c “SELECT
column_name,
count(*) as query_count
FROM
pg_stat_statements,
regexp_split_to_table(query, ‘\s+’) as token
WHERE
token ILIKE ‘%WHERE%’ OR
token ILIKE ‘%JOIN%’ OR
token ILIKE ‘%ON%’
GROUP BY
column_name
ORDER BY
query_count DESC
LIMIT 20;
”
# 3. 创建合适的索引
# 为频繁查询的列创建索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_orders_user_id_idx ON fgedu_orders(user_id);”
# 为复合查询创建复合索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_orders_user_id_created_at_idx ON fgedu_orders(user_id, created_at);”
# 为范围查询创建索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_orders_created_at_idx ON fgedu_orders(created_at);”
# 为唯一约束创建唯一索引
$ psql -U fgedu -d fgedudb -c “CREATE UNIQUE INDEX fgedu_users_email_idx ON fgedu_users(email);”
# 4. 验证索引创建
$ psql -U fgedu -d fgedudb -c “\d fgedu_orders”
# 5. 测试索引效果
$ psql -U fgedu -d fgedudb -c “EXPLAIN ANALYZE SELECT * FROM fgedu_orders WHERE user_id = 1 AND created_at > ‘2026-04-01’;”
4.2 索引维护案例
4.2.1 生产环境索引维护
# 1. 查看索引碎片
$ psql -U fgedu -d fgedudb -c “SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
CASE WHEN pg_total_relation_size(c.oid) > 0 THEN
round(100 * pg_indexes_size(c.oid) / pg_total_relation_size(c.oid), 2)
ELSE 0 END AS index_ratio
FROM
pg_class c
JOIN
pg_index i ON c.oid = i.indrelid
JOIN
pg_indexes ix ON c.relname = ix.tablename AND i.indexrelid::regclass::text = ix.indexname
WHERE
c.relkind = ‘r’ AND
schemaname = ‘public’
ORDER BY
pg_indexes_size(c.oid) DESC;
”
# 2. 清理索引碎片
$ psql -U fgedu -d fgedudb -c “VACUUM ANALYZE fgedu_users;”
$ psql -U fgedu -d fgedudb -c “VACUUM ANALYZE fgedu_orders;”
# 3. 重建碎片化严重的索引
$ psql -U fgedu -d fgedudb -c “REINDEX INDEX fgedu_orders_user_id_created_at_idx;”
# 4. 查看未使用的索引
$ psql -U fgedu -d fgedudb -c “SELECT
schemaname,
tablename,
indexname
FROM
pg_stat_user_indexes
WHERE
schemaname = ‘public’ AND
idx_scan = 0
ORDER BY
tablename, indexname;
”
# 5. 删除未使用的索引
$ psql -U fgedu -d fgedudb -c “DROP INDEX IF EXISTS fgedu_orders_old_idx;”
4.3 索引优化案例
4.3.1 复杂查询索引优化
# 1. 原始查询
SELECT
c.id, c.name, c.email,
COUNT(o.id) as order_count,
SUM(o.amount) as total_amount
FROM
fgedu_customers c
LEFT JOIN
fgedu_orders o ON c.id = o.customer_id
WHERE
c.active = true
AND o.created_at >= ‘2026-01-01’
GROUP BY
c.id, c.name, c.email
HAVING
COUNT(o.id) > 5
ORDER BY
total_amount DESC
LIMIT 20;
# 2. 分析执行计划
$ psql -U fgedu -d fgedudb -c “EXPLAIN ANALYZE SELECT c.id, c.name, c.email, COUNT(o.id) as order_count, SUM(o.amount) as total_amount FROM fgedu_customers c LEFT JOIN fgedu_orders o ON c.id = o.customer_id WHERE c.active = true AND o.created_at >= ‘2026-01-01’ GROUP BY c.id, c.name, c.email HAVING COUNT(o.id) > 5 ORDER BY total_amount DESC LIMIT 20;”
# 3. 优化措施
# 创建部分索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_customers_active_idx ON fgedu_customers(id) WHERE active = true;”
# 创建复合索引
$ psql -U fgedu -d fgedudb -c “CREATE INDEX fgedu_orders_customer_id_created_at_idx ON fgedu_orders(customer_id, created_at);”
# 4. 优化后的查询
SELECT
c.id, c.name, c.email,
order_stats.order_count,
order_stats.total_amount
FROM
fgedu_customers c
LEFT JOIN (SELECT
customer_id,
COUNT(*) as order_count,
SUM(amount) as total_amount
FROM
fgedu_orders
WHERE
created_at >= ‘2026-01-01’
GROUP BY
customer_id
HAVING
COUNT(*) > 5
) as order_stats ON c.id = order_stats.customer_id
WHERE
c.active = true
ORDER BY
order_stats.total_amount DESC
LIMIT 20;
# 5. 验证优化效果
$ psql -U fgedu -d fgedudb -c “EXPLAIN ANALYZE SELECT c.id, c.name, c.email, order_stats.order_count, order_stats.total_amount FROM fgedu_customers c LEFT JOIN (SELECT customer_id, COUNT(*) as order_count, SUM(amount) as total_amount FROM fgedu_orders WHERE created_at >= ‘2026-01-01’ GROUP BY customer_id HAVING COUNT(*) > 5) as order_stats ON c.id = order_stats.customer_id WHERE c.active = true ORDER BY order_stats.total_amount DESC LIMIT 20;”
Part05-风哥经验总结与分享
5.1 索引优化最佳实践
PostgreSQL索引优化的最佳实践:
- 选择合适的索引类型:根据查询模式选择合适的索引类型
- 优化索引键顺序:在复合索引中,将最常用的列放在前面
- 避免过度索引:不要创建过多的索引,会增加维护成本
- 使用部分索引:对于特定条件的查询,使用部分索引
- 使用表达式索引:对于基于表达式的查询,使用表达式索引
- 定期维护索引:定期重建或重新索引,减少索引碎片
- 监控索引使用:定期查看索引使用情况,清理未使用的索引
- 分析执行计划:使用EXPLAIN ANALYZE分析查询的执行计划,确认索引是否被使用
5.2 风哥经验分享
1. 索引设计要合理:根据查询模式创建合适的索引,避免过度索引
2. 索引维护要定期:定期重建或重新索引,减少索引碎片
3. 索引使用要监控:定期查看索引使用情况,清理未使用的索引
4. 执行计划要分析:使用EXPLAIN ANALYZE分析查询的执行计划,确认索引是否被使用
5. 数据分布要了解:了解列的数据分布情况,选择合适的索引类型
6. 索引成本要权衡:权衡索引的维护成本和查询收益
通过合理的索引优化,可以显著提高系统的性能和稳定性,为业务提供更好的支持。from PostgreSQL视频:www.itpux.com
5.3 常见问题与解决方案
PostgreSQL索引优化常见问题与解决方案:
症状:创建了索引,但查询没有使用索引
解决方案:
– 分析执行计划,确认索引是否被使用
– 检查查询条件,确保索引键被正确使用
– 检查数据分布,确保索引选择性高
– 运行ANALYZE更新统计信息
# 常见问题2:索引过多
症状:表上有过多的索引,影响插入、更新和删除性能
解决方案:
– 分析索引使用情况,删除未使用的索引
– 合并多个索引为复合索引
– 考虑使用部分索引或表达式索引
# 常见问题3:索引碎片严重
症状:索引碎片严重,影响查询性能
解决方案:
– 运行VACUUM ANALYZE清理碎片
– 重建索引
– 考虑使用pg_repack工具
# 常见问题4:复合索引顺序不合理
症状:复合索引的顺序不合理,影响查询性能
解决方案:
– 根据查询模式调整索引键顺序
– 将最常用的列放在前面
– 考虑创建多个复合索引,覆盖不同的查询模式
# 常见问题5:索引选择性低
症状:索引选择性低,查询性能差
解决方案:
– 分析数据分布,选择选择性高的列创建索引
– 考虑使用复合索引
– 对于低选择性的列,考虑使用部分索引
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
