ITPub博客

首页 > 数据库 > 数据库开发技术 > 胖头鱼的技术专栏-488 索引比列还多的那张表(20261112)

胖头鱼的技术专栏-488 索引比列还多的那张表(20261112)

原创 数据库开发技术 作者:yhw1809 时间:2026-11-11 22:28:30 0 删除 编辑

胖头鱼的技术专栏-488 索引比列还多的那张表(20261112)

作者:胖头鱼的鱼缸(尹海文)
Oracle ACE Pro: Database
PostgreSQL ACE
10年+数据库行业经验
拥有OCM 11g/12c/19c、MySQL 8.0 OCP、Exadata、CDP等认证
墨天轮MVP,ITPUB认证专家
圈内拥有“总监”称号,非著 名社恐(社交恐怖分子)
全网同名:胖头鱼的鱼缸
ITPUB:yhw1809
除授权转载并标明出处外,均为“非法”抄袭

914fcc7ad57defa7868c3be1ca7fb4f5.jpg

最近,一个以前的客户找到我,开场就一句:现在这帮 DBA 做优化,只会加索引。

我说你先别急着发火,把表结构发我看看。发过来一看,有几张核心表,索引数量已经超过列数了。列就那么几个,索引一排一排往上堆,同一个列在三个索引里都露过面。

后果也很直接:表和索引占的空间涨得飞快,白天业务一跑 DML,明显能感觉到慢。

聊到最后,客户补了一句:其实还是怀念以前你给我们做的那套优化,从业务上改,不是从数据库上改。

这两句话听着是两件事,其实是一件事: 只会加索引,和只盯着数据库,是同一个毛病。 眼睛都在库里,没人往上看业务是怎么用库的。

一、多一个索引,多一次写

索引在库里是一份独立的结构,排好序,单独占地方,跟着基表一起改。

插入一行数据,不是写一处就完了:表里写一份,表上每个索引各写一份。更新只要碰到索引列,对应的索引项就得先删再插(PostgreSQL 里不改索引列的更新走 HOT,索引可以不跟着动,这是少数派)。删除更麻烦,索引上的条目得留标记,等清理。

一处写变成一堆写,日志量跟着往上翻,缓冲池里索引页和热数据抢位置,备份、恢复、DDL、统计信息收集全按对象数量往上走。

fig1writeamplification.png

图 1 一次 INSERT 到底写了几处

还有个不太被提的代价:索引多了,优化器可选的路径就多,估算误差一放大,计划波动更难收敛。同一条 SQL 早上一个计划、晚上另一个计划,这种事在索引堆得多的表上更常见。

索引是拿写换读。摊子铺得越大,这笔买卖越亏。

不是索引越少越好,是 少建几个、每个多干点活。要做到这一点,先得知道每个索引到底在给谁干活。

二、先把这张表的 SQL 拉出来

客户缺的其实不是索引,是没人把这张表上的 SQL 拉出来看过一遍。

做法不复杂:把一段时间内的语句统计抓出来(Oracle 的 AWR、PostgreSQL 的  pg_stat_statements、MySQL 的慢日志和  performance_schema 都能拿到),筛出打在这张表上的语句,再按过滤列的组合归类。

归类完,通常会看到三种东西。

第一种,一条月报 SQL 养着一个索引。这条 SQL 一个月跑一次,索引天天跟着维护。

第二种,几条 SQL 的过滤列高度重合,本来可以并进一个复合索引,结果是各建各的。

第三种,索引建完就没人用过。Oracle 可以开  dba_index_usage 的监控看,PostgreSQL 直接查  pg_stat_user_indexes 里的  idx_scan。

fig2indexledger.png

图 2 索引台账长什么样

一张表的索引清单,应该是这张表 SQL 清单的投影,不是 DBA 手写的记录。

索引数量是个结果,不是指标。 拿索引数量当优化产出,方向从一开始就错了。

三、列序排错,索引白建

客户那堆索引里,绝大部分是"一列一个"的单列索引,一条 SQL 配一个。这也是索引数能超过列数最典型的来路:列就那么多,一个列被拆进好几个索引里反复出现。

复合索引的道理众所周知:靠最左前缀干活。一个  (a,b,c) 的索引,能服务  a、 a+b、 a+b+c 三类条件组合,一次区间扫描加一次回表,一条索引干三份活。

fig3prefixmatrix.png

图 3 (a,b,c) 能覆盖哪些条件组合

索引在物理上是按 a、然后 b、然后 c 排好的一棵树。b 没有约束住,c 在树里就是断开的:每个 b 下面各自有序,一跨过 b 就乱了。 a+c 这种条件,索引只能靠 a 定位,c 只能在索引里做过滤,跟真正拿 c 去定位差着一档。

Oracle 的 index skip scan、MySQL 8.0.13 之后的 skip scan,在前导列基数很低的时候能跳过中间列,把 c 也拿来定位。但要不要走由成本模型定,不是想用就能用;PostgreSQL 这边没有对应的能力。 别把它当通用解。

同理,a 本身是范围条件的时候( a > 100 and b = 5),b 和 c 也只能当过滤用。列序规则里"等值在前、范围在后",根子就在这。

那几个单列索引呢?它们靠索引合并(Oracle 的索引合并与位图转换、PostgreSQL 的  BitmapAnd、MySQL 的 index merge)也能覆盖 7 种条件组合,看着更灵活。代价在三份存储、三份写,加上合并行号再回表这一步,返回行数一多就不划算了。

fig4singlevscomposite.png

图 4 同样的覆盖面,两种建法的代价

一个索引干三件事,比三个索引各干一件事便宜,这在写多的表上差得更明显。

前提是这几条 SQL 的前缀真能叠在一起。叠不上的硬并,只会白存一截宽列,还不如各建各的。

列序怎么排,四条:

  1. 等值条件在前,范围条件在后。范围一出现,后面列的有序性就用不上了。
  2. 等值列之间按 复用度排:哪一列在这张表最多 SQL 的等值条件里出现,就放最前面。它能让一个索引多服务几条 SQL,索引总数压得下来,空间和 DML 代价跟着降。复用度相当的,再比选择性。
  3. 有 order by、group by 的,排序列接在等值列后面,能省一次排序。
  4. 别把常用列一股脑堆进去。列越宽,索引越接近一张窄表,前缀用不到的部分全是白存的。

SQL 那一侧要跟着动。这里有个传了很多年的误解: WHERE 里条件的书写顺序,不影响走不走索引,优化器不看这个。真正要调的是索引列序,以及让 SQL 把前缀列的等值条件补齐。

具体动作就几条:业务上本来就确定的常量(租户号、机构号、状态、类型)显式带进 SQL,别让索引第一列悬空;索引列别用函数或者表达式包住;范围条件和等值条件别混在同一层; like '%x' 这种前置通配符,直接把这一列废掉。

这几条改起来要业务配合,改 SQL、改传参、改接口。

列序不是照着建表时的字段顺序排的,是按这张表最常被怎么查排的。

说白了,复合索引像抽屉里的分隔盒,从第一格开始用,前面几格能单独使;一堆单列索引就是一堆独立小盒,每个只装一样东西,柜子很快就塞满了。

例外也得说清:唯一约束和主键该建就建;写多读极少、SQL 又杂的表,先砍数量比抠列序更划算;几列合起来的选择性远高于任何单列时,宽一点的复合索引反而更省;需要免回表的覆盖索引,热点查询值得单独建一个。

四、客户怀念的那套优化,压根没碰参数和索引

回到客户那段话的另一半。

那套优化干活的方式不一样:先看业务怎么用库,再看库里怎么执行。改的多半是用法,不是配置。

fig5threelayers.png

图 5 优化往下走,收益和难度一起涨

常见的几类,做过这类活的人应该都眼熟:分页用大偏移,翻到后面几页越来越慢;循环里一条一条查,一次列表页打几百次库;报表直接压在业务主库上;字段类型不一致,条件上做了隐式转换; select * 把大字段全拉出来;时间表只增不归档,越跑越厚。

还有两个更隐蔽的。一个是编号不用序列,拿一张表存当前最大值,每次插入前先 update 加一,再把值查出来用。所有插入都挤在同一行上排队,行锁一个一个等,并发一上来就堵住。

另一个是同一件事的多条 DML 各提交各的:主表一条、明细三条,每条都 commit。一次业务操作要等四回日志同步,网络往返和落盘次数一起翻上去,业务侧还看不出哪里慢。

这些改完,一批索引的需求自己就没了。 很多索引是在替不合理的用法兜底,用法改了,兜底的东西就不需要了。

索引该清的时候也别手快。Oracle 可以先把索引改成不可见(invisible index),观察一段;PostgreSQL 没有这一手,就盯着  idx_scan,而且要跨过月结、季结、年末这些跑批周期,确认整个业务周期里都是 0 再动手。直接 drop 的那种,出了事只能重建,大表重建索引的时间不好估。

参数和索引是给现有用法打补丁,业务用法才是那块布。

五、为什么 DBA 只会加索引

这事不能全怪 DBA。

加索引是不用改代码、不用排期、不用说服业务、不用回归测试、当天就能上线的优化动作,而且一时想不出第二个。建完,语句快了,事情就算交代了。

改 SQL 呢?要跨团队,要业务确认口径,要回归测试,要排窗口,还要担一句"改坏了算谁的"。

跨团队那一步,考的不只是技术。得跟开发讲清为什么改,跟业务把口径对明白,跟领导要排期:交流能力、人际关系、流程走到哪一环,哪一样差一点都推不动。同样是一个下午,加索引能加完十个,改 SQL 可能连评审都没过。

所以"只会加索引"不一定是能力问题,是 成本问题,而且是组织里被奖励出来的行为: 它见效快、责任轻、还能立刻被看见。

总监这个称号挂我头上有些年头了,说句自嘲的:要论性价比,加索引在我见过的优化手段里排第一。

六、给索引立个台账

想把主动权拿回来,三件事就够:

  1. 每个索引登记一条:服务哪几条 SQL、列序为什么这么排、谁在什么时候加的、近一个周期用过几次。
  2. 上线前过一道 SQL 评审,把"能并进现有复合索引"和"不加索引也能解决"都算加分项,别当成优化失败。
  3. 定期清理,按上一节那个观察期走。

这三件都是事后收拾,管得住增量,管不了根子。再往前一步,DBA 该进到业务的库表逻辑设计阶段去:哪些是一类实体、哪些只是扩展属性、表怎么拆、键怎么定。这一步定歪了,后面加多少索引都是在补窟窿。

川序这套东西是我自己带着 AI 一点点写出来的,功能和表也是一路加一路长:面多了加水,水多了加面,字段和表越铺越散。最后还是我一条条要求它按可扩展的实体化设计重来,才收住。AI 写表尚且这样,业务方顺手画出来的表更难说。

回到开篇那张表,目标不是把索引删干净,是 让每个索引都能说出自己服务谁、列序为什么这么排。说不出来的,就该进观察名单了。

总结

索引数量不是优化成果。能用一个复合索引盖住几条查询的前缀,就别用三个单列去凑;该有几个、列序怎么排,取决于这张表被怎么查。

索引和参数能解决的问题有边界。省下来的大头多半在业务怎么用库上:分页怎么写、循环里怎么查、报表打哪儿、表要不要归档。这部分最费口舌,改一次能管很久。

老规矩,知道写了些啥。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31466763/viewspace-3188654/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2017-06-07

  • 博文量
    392
  • 访问量
    417154