首页 / it.itcdj.com / 正文

踩过坑才懂的数据库索引真相

上周跟个做后端的朋友在珠江路吃小龙虾,他刚把笔记本拍在桌上,说昨天线上慢查询把他坑惨了。查了二十分钟才发现,他给订单表建的联合索引,压根没被用上。

别把索引当万能加速键

别把索引当万能加速键

很多人对索引的认知停留在”加了就快”,上来就给所有where条件的字段挨个加单列索引,觉得多建几个总没坏处。真不是。

每次insert、update、delete的时候,数据库都要同步维护所有关联的索引树,节点拆分、排序这些操作全是开销。之前有个小团队给订单表的14个字段全加了单列索引,高峰期写入接口超时率直接飙到20%,运维差点把他们办公室的网线拔了。你省下来的查询时间,可能转头就被写入的额外开销吃干净。

数据库默认用的B+树索引,这事没那么玄,就是为了减少磁盘IO的次数,树的高度越低,查的时候读的磁盘块越少,速度自然越快。

数据库B树索引节点拆分示意图
数据库B树索引节点拆分示意图

有人问为什么不用哈希索引?说白了,哈希只适合等值查询,你要查个时间范围、金额区间,直接抓瞎,总不能把所有可能的区间都算一遍哈希值吧?大部分业务场景都有范围查询的需求,所以哈希索引永远成不了主力。

别乱加索引。

真的。

联合索引的最左匹配不是背口诀

那个踩坑的朋友,建的联合索引是(user_id, order_time, amount),结果他写的查询是where order_time > ‘2024-01-01’ and amount < 100,跑了12秒,他还喊冤说我明明建了三个字段的联合索引啊,怎么就失效了?

最左匹配从来不是让你死背”必须从索引第一个字段开始查才有用”的口诀,本质是B+树的排序规则——索引是按第一个字段先排序,第一个字段相同的情况下再按第二个排,第二个相同再按第三个。你连第一个字段的过滤条件都没有,数据库根本不知道从树的哪个位置开始找,只能全表扫。

数据库联合索引最左匹配规则示意图
数据库联合索引最左匹配规则示意图

那是不是只要带了第一个字段就一定能用索引?也不是。比如user_id这个字段,如果你存的都是同一个用户的测试数据,区分度极低,优化器算下来觉得走索引还要回表,不如直接扫全表快,那索引照样不会被用上。哦对,还有那种like ‘%南京’的查询,前模糊,索引根本用不上,因为索引是按字符串从左到右排的,你前面的字符是模糊的,没法定位范围。

有人为了让前模糊查询用上索引,特意把字段反过来存,查询的时候也把关键词反过来匹配,折腾半天,最后发现业务还要支持搜索多个关键词,白忙活。这种时候不如直接上全文索引,别死磕普通B树索引。

线上慢查询的锅未必在索引

很多人一看到慢查询日志,第一反应就是加索引,根本不看执行计划。上次那个朋友的慢查询,最后查出来除了没带user_id的过滤条件,还有个原因是他的事务开了快20秒,等行锁等了半天,根本不是查询本身慢。

你先把执行计划跑出来,看到type是all就慌,说全表扫了要加索引。不对。如果你的表只有几千行,全表顺序扫的速度比走索引还快——因为走索引还要先查索引树,再拿着主键回原表捞数据,两次随机IO的开销,比顺序扫全表的一次顺序IO高多了。

数据库慢查询执行计划explain输出截图
数据库慢查询执行计划explain输出截图

回表这个事,说白了就是你建的索引里没存你要查的所有字段,就得拿着主键去原表再捞一遍。那怎么避免?覆盖索引啊,把你要查的字段都塞进索引里,查的时候直接从索引里拿数据,不用回表,速度能快好几倍。但是代价是什么?索引的体积会变大,写入的时候维护的成本也会涨。

那个朋友最后怎么改的?他把原来的联合索引调整了顺序,把常用的查询条件放在最前面,又把要返回的三个字段加进索引里做覆盖索引,改完之后查询从12秒降到了28毫秒,但是写入的QPS掉了大概8%。他纠结了一晚上,最后因为这个接口是读多写少的报表接口,也就接受了。

那天小龙虾都凉了,他还在那翻之前的慢查询日志,说原来之前踩的坑,全是没算清楚取舍。