首页 / 业界 / 正文

数据库索引的代价远大于你以为的

上周帮朋友排查线上慢查询,他的表只有不到10万行数据,一个带条件的查询居然跑了12秒。

我拉了执行计划一看,他给三个完全不常用的字段加了索引,偏偏最常用来过滤的用户ID字段,没加。

这事儿太常见了。很多人刚学数据库的时候,第一反应是只要慢就加索引。加的时候爽,后面埋的坑能踩半年。

索引不是加得越多越好

之前见过一个运维同学,为了让内部报表查询快一点,给一张日增10万行的订单表加了7个索引。结果每天凌晨的批量写入任务,从原来的40分钟跑到了3个小时。

为啥?

每插一行数据,数据库要同时给7个索引插入对应的节点,还要做页分裂,还要动态调整B+树的层级结构。写入性能的损耗是线性叠加的,不是加一两个索引就可以忽略不计的。

还有空间成本。一个亿级行的大表,单个索引的文件大小有时候能占到原表数据体积的三分之一甚至更多。你当初买云存储的时候算的是用户数据的容量,结果一半的空间被半年没用到过的无效索引占了。

备份的时候更麻烦,索引文件越大,备份窗口越长,遇到故障恢复的时间也越久。

数据库B+树索引层级结构示意图

很多人以为索引只对查询速度有帮助,根本没算过这些隐形成本。就像你家里为了找东西方便,每个抽屉都贴满分类标签,结果每次买新东西回来,要给所有标签都更新一遍,累都累死。

索引选错了比没加还糟

之前有个开发跟我吐槽,说他给订单状态字段加了索引,为啥查询还是慢得离谱。

我问他,这个字段总共有几个取值?

他挠头想了想,就待支付、已支付、已完成、已取消四个。

这加了有啥用?

索引的效率本质上靠字段的区分度撑着,区分度太低的字段,优化器算下来走索引的成本比全表扫描还高,干脆就放弃用索引,你白忙活一场。

更坑的是联合索引建错顺序的场景。比如你建了(用户ID,订单状态,创建时间)的联合索引,结果写SQL的时候只过滤了创建时间,那这个索引根本匹配不上,等于白建。很多人背最左匹配原则背得滚瓜烂熟,一到线上写SQL就漏了最左的条件。

还有隐式类型转换的老坑。比如你的手机号字段是字符串类型,写查询的时候直接传了个数字进去,数据库默默做类型转换,索引直接失效。之前有人因为这个,一个查询从100毫秒跑到了10秒,排查了半天才找到原因。

数据库多索引写入耗时对比图
数据库多索引写入耗时对比图

很多人遇到索引失效的问题,第一反应是再加个新索引。结果越加越多,越查越慢,陷入死循环。

索引只加不删迟早出问题

我之前翻一个跑了5年的老项目库表,发现有个核心交易表居然挂了12个索引。

拉过去三个月的执行记录一看,有6个索引压根没被用到过。

为啥没人删?

怕啊。万一哪个藏得很深的边缘报表查询用到了,删了之后出问题谁背锅?

但这些没用的索引,每天都在拖慢写入速度,占着存储,备份的时候还要多花十几分钟。

麻烦在于,没人能100%拍胸脯说某个索引完全没用。靠谱的做法是定期拉慢查询日志,把所有被用到的索引列出来,剩下的先标记成待观察状态,观察一两周确实没有查询命中,再慢慢下线。

还有那种线上大表加索引的坑。之前见过有人图省事,给一张亿级行的表直接执行加索引命令,整个数据库被锁了20分钟,全站用户都付不了款,最后只能紧急回滚。现在谁还敢这么干啊,要么用在线加索引的工具,要么等业务低峰慢慢搞,慢一点没关系,至少不影响线上用户。

说真的,数据库索引这东西,刚入门的时候觉得简单,不就是给字段加个标记,查的时候快一点嘛。真到线上踩过几次慢查询、几次写入超时的坑,才知道每建一个索引,都是要算清楚的取舍。

你当初给查询省的那几百毫秒,最后都会在写入的时候加倍还回来。

没有凭空变出来的性能。

对吧?