You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL 9.6中未使用索引的判定与元数据有效性检查

PostgreSQL 9.6未使用索引的判断与处理指南

先还原下你的场景:你运行了这条SQL来排查public schema里的未使用索引:

select t.tablename AS "relation", indexname, c.reltuples AS num_rows, pg_relation_size(quote_ident(t.tablename)::text) AS table_size, pg_relation_size(quote_ident(indexrelname)::text) AS index_size, idx_scan AS number_of_scans, idx_tup_read AS tuples_read, idx_tup_fetch AS tuples_fetched FROM pg_tables t LEFT OUTER JOIN pg_class c ON t.tablename=c.relname LEFT OUTER JOIN ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, psai.idx_scan, idx_tup_read, idx_tup_fetch, indexrelname, indisunique FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class ipg ON ipg.oid = x.indexrelid JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid ) AS foo ON t.tablename = foo.ctablename WHERE t.schemaname='public' and idx_scan = 0 ORDER BY --1,2 --6 5 desc ;

结果出来一堆number_of_scans、tuples_read、tuples_fetched全为0的索引,接下来我一步步给你解答疑问:

一、这些索引能直接删除吗?

绝对不能直接删! 先踩刹车,得先排除几种关键情况:

  • 主键/唯一约束索引:你的查询里虽然拉了indisunique字段,但没过滤掉这类索引。主键和唯一约束依赖的索引是用来保证数据完整性的——哪怕从来没被查询用到,删了会直接破坏数据约束,导致重复数据插入,绝对碰不得。
  • 低频但必要的索引:有些索引只在每周报表、月度对账这类周期性任务里用,而PostgreSQL的统计计数器是从实例重启后开始累积的,如果你的数据库刚重启过,或者统计周期没覆盖到这些任务的执行时间,就会显示0扫描。
  • 写操作的代价:索引会拖慢INSERT/UPDATE/DELETE的速度,要是确定真没用,删了确实能提升写性能,但前提是100%确认它没有任何业务场景依赖。

二、会不会是元数据过期导致统计不准?

当然有可能!PostgreSQL的pg_stat_all_indexes统计数据有几个“坑”:

  • 实例重启清零:每次PostgreSQL重启,所有统计计数器都会被重置为0。如果你的数据库最近刚重启过,那这个查询结果完全没参考价值,得等业务跑过一个完整周期再看。
  • 新创建的索引:如果索引是刚建的,自然还没被扫描过,显示0是正常的,得观察一段时间。
  • 统计数据未持久化:PostgreSQL 9.6没有像新版本那样的持久化统计功能,统计数据只存在于内存中,重启就没了,所以如果你的实例运行时间短,统计数据也不完整。

三、怎么检查确认这些索引是否真的没用?

给你一套实操步骤,按顺序来:

1. 先筛选出非约束类索引

先修改你的查询,把主键、唯一约束的索引标出来,直接排除:

select 
  t.tablename AS "relation", 
  indexname,
  case when indisprimary then '主键索引' when indisunique then '唯一约束索引' else '普通索引' end as index_type,
  c.reltuples AS num_rows, 
  pg_relation_size(quote_ident(t.tablename)::text) AS table_size, 
  pg_relation_size(quote_ident(indexrelname)::text) AS index_size, 
  idx_scan AS number_of_scans, 
  idx_tup_read AS tuples_read, 
  idx_tup_fetch AS tuples_fetched 
FROM pg_tables t 
LEFT OUTER JOIN pg_class c ON t.tablename=c.relname 
LEFT OUTER JOIN ( 
  SELECT 
    c.relname AS ctablename, 
    ipg.relname AS indexname, 
    x.indnatts AS number_of_columns, 
    psai.idx_scan, 
    idx_tup_read, 
    idx_tup_fetch, 
    indexrelname, 
    indisunique,
    indisprimary
  FROM pg_index x 
  JOIN pg_class c ON c.oid = x.indrelid 
  JOIN pg_class ipg ON ipg.oid = x.indexrelid 
  JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid 
) AS foo ON t.tablename = foo.ctablename 
WHERE t.schemaname='public' and idx_scan = 0 
ORDER BY index_size desc;

结果里标为“主键索引”“唯一约束索引”的直接跳过,不用考虑删除。

2. 查看数据库重启时间

先确认统计数据的起始点:

SELECT pg_postmaster_start_time();

如果这个时间离现在很近,比如才几天,那先等一周(覆盖你的业务周期)再重新查询,看看这些索引的扫描次数有没有变化。

3. 核对索引定义,排查特殊场景

有些索引看起来没用,但实际上是为特定查询设计的,比如表达式索引、部分索引,先查索引定义:

SELECT indexdef FROM pg_indexes WHERE schemaname='public' AND indexname='你的索引名';

比如看到CREATE INDEX idx_users_lower_email ON users (lower(email));,那这个索引是给WHERE lower(email) = 'xxx'这类查询用的,得确认业务里有没有这类查询,不能光看扫描次数就删。

4. 临时禁用索引测试(谨慎操作)

对于拿不准的普通索引,可以先禁用它,观察业务情况:

ALTER INDEX 你的索引名 DISABLE;

接下来观察几天:有没有业务报错?有没有慢查询突然增多?如果一切正常,那这个索引就可以安全删除;如果出现问题,立刻启用:

ALTER INDEX 你的索引名 ENABLE;

⚠️ 注意:绝对不能禁用主键或唯一约束索引!会导致约束失效,数据完整性被破坏。

5. 检查慢查询日志

如果你的数据库开启了慢查询日志,可以搜索对应表和索引字段的查询,看看有没有查询计划用到过这个索引——有些低频查询可能没被统计到,但确实存在。


内容的提问来源于stack exchange,提问作者user2671057

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:04:11