2000 万的表,索引怎么建 ?

共 1896字,需浏览 4分钟

 ·

2020-09-23 10:19

点击蓝色“有关SQL”关注我哟

加个“星标”,天天与10000人一起快乐成长



建索引,别看是 CREATE INDEX 那么简单。建好了,可真不是简单的事儿。

你看,为建索引,还有作者专门写了一本书。唯一一本能从索引本质说起来的书,从磁盘随机读,顺序读说起,从数据库索引结构剖析,从浅入深,从局部入手,全盘考量,最终选择合适的索引,说它是系统工程,可真一点没错。

当然,数据库设计与开发中,类似的系统工程思维,真有不少,看我前面一篇帖子,就是最好的证明:

作为面试官,我最常用的两类SQL数据库面试题

今天,我要分享的,是针对千万级别的表,应该慎重考虑的一种索引及其原理。

举个例子:

DECLARE @ORDER_ID VARCHAR(15) = 'A-14RX7',
        @ATTR NVARCHAR(128) = 'PRODUCT_NAME'

SELECT A.EVENT_ID,
    A.ORDER_ID,
    A.ATTR,
    A.VAL1,
    A.VAL2
FROM EVENT B 
    INNER JOIN EVENT_LOG A 
        ON A.EVENT_ID = B.ID 
WHERE A.ORDER_ID = @ORDER_ID 
    AND A.ATTR = @ATTR
ORDER BY B.BEGIN_DATE ASC 

其中 EVENT 的数据量有上百万,EVENT_LOG 的数据量有2000多万

此时的索引情况是 EVENT 在 ID 上有唯一索引,而 EVENT_LOG 上有 针对 EVENT_ID 做了非唯一索引。

就上面这段脚本,运行时间超过了20秒。

很可能大家看到这样一个 Join, 两边都建立了索引,认为查询效率应该没有问题。你看,索引都建了,也都按照索引去走了。那么为什么效率还那么慢呢?其实,就是索引无效。

比如我第一次建索引的时候,就犯了这么个错误:

CREATE INDEX IDX_EVT_ATTR_VAL 
ON EVENT_LOG(ORDER_ID,ATTR
INCLUDE(EVENT_ID,VAL1,VAL2)

众所周知,索引呈树结构组织,最底层的数据页,存储索引真实的数据,还有指向堆表的地址或者主键值。


上图就是典型的索引结构图。索引的键值,都存储在 Page3, Page4(当然实际的索引页复杂的多),俗称叶子页,而中间的那些数据页,存储的数据,都是到达最终叶子页的路径,俗称中间页。最顶层的那个页,称为顶点(root)页。

本次查询中,从 EVENT_LOG 查询的字段不多,因此把这些字段都存到索引叶子结点,明显能够减少回表的成本。我真就这么干了。

潇洒的回车执行后,去泡了杯茶回来,结果一看,我楞眼了,索引还没建完。于是赶紧停掉,好险。都已经 2 分钟过去了。

仔细看了下 EVENT_LOG 表结构,才发现 VAL1,VAL2 居然是 2 个超长字符串,将其放在叶子页,明显加大了索引的叶子页的空间,原本1000个页搞定的事情,说不定就长成了10万个页,使得索引无效。

再看下表统计,有2000多万条数据。而真实逻辑是,1 个 EVENT_ID 在 EVENT_LOG 中对应的数据,可能有上百条,但在 EVENT_ID 加 ATTR 的限定下,数据条数不会超过 10 条。

于是,果断放弃覆盖索引,改成一般索引:

CREATE INDEX IDX_EVT_ATTR_VAL 
ON EVENT_LOG(ORDER_ID,ATTR

果然,不到 40 秒,索引建立完毕,最开始的查询,也秒出。

所以,大家千万别以为,索引建了就一定 有用,建错索引,带来的后果很严重。

PS: 看到这里的朋友,顺便说下最近我的研究情况。近半月全身心在看 TPC 的论文,模拟 TPC 各类数据库测压标准。能够将数据库支撑的QPS( Query Per Second,即每秒查询数) 准确估算出来,才能防范于未然。相关的文章也在整理当中。回复“TPCH”可拿到数据仓库的测试指南,一起研究。



--完--





往期精彩:


本号精华合集(二)

如何写好 5000 行的 SQL 代码

如何提高阅读 SQL 源代码的快感

我在面试数据库工程师候选人时,常问的一些题

零基础 SQL 数据库小白,从入门到精通的学习路线与书单









浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报