这是前几天同事做的技术分析,讲的挺不错,现在整理一下
本文中数据库操作与数据库工作原理描述,都是经过简化的,实际应用中,数据库本身也会提供各种优化措施。
数据库线性搜索
假设现在又一个几百万条数据的购物车销售记录表,表结构类似下表
id | userid | productid | dealdate | promotions | dealstate |
---|---|---|---|---|---|
1 | 10876 | 100003 | 14567776667 | -9.5 | 成交 |
2 | 93772 | 188293 | 14566636777 | -8 | 关闭 |
Q1: 查询 userid = 100003 的用户,所有购买记录。
A1: 在没有做任何优化的情况下,数据库需要从 id = 1 的记录开始向下扫描,直到将整个�表扫描完毕,才可以检索出所有�符合要求的记录。�可想而知,当表中记录非常多的时候,需要�非常多的磁盘 I/O 才能完成此次查询。在实际应用中,例如 x 东用户查询自己的购买记录这种需求,对服务器来说就是灾难啊。
使用索引提高查询效率
使用�单个字段建立索引
回到 Q1 这个问题,如果我们在 userid 这个字段上建立索引,那么会产生一个 userid 到�记录位置的映射。�这也是一个�表,在这个表中�查找某个 userid,就能够找到所有符合该 userid �的记录在表中的位置,然后直接访问这些位置读取记录就可以了。
使用多个字段建立索引
�Q2: 如果遇到一个购物达人,购物记录有上万条,通过 userid 这个索引查询该用户的所有搜索记录,就�会产生上万次磁盘 I/O,如果是机械硬盘,单单是寻道时间就�把这次查询卡爆了,即使换 SSD,能够服务的用户�也是很有限的。
A2: �遇到这种情况,我们可以将该用户的购物记录分页,每次返回十几条展示出来就可以了。向后翻页的时候,可以从 offset 开始,再检索出十几条返回到前端展示。特别是移动端与桌面端�可能会跳到第10页这种需求不同,向来都是上拉加载更多,是线性加载的,�对数据库压力就更小了。(PS. 跳转到第几页这种需求,需要 count 数据的总数,通常情况下,这个操作也需要将所有记录读取一遍,效率很低。)
这种需求下,一般会将购物记录按照时间顺序返回给用户,我们只需要用 userid 和 dealdate 这两个字段建立一个复合索引{userid,dealdate}就可以了。(建立索引时,指定 dealdate 按照降序排列)
检索时,查找“近一月购物记录”,只要按照时间检索一些数据出来就可以了。
不应该建立�什么样的索引
如果�我们需要查询成交状态,是不是可以�建立一个{dealstate}这样的索引呢?
答案是,没什么用。
如果检索 dealstate = 成交 这样的记录,如果扫描整个表,需要检索的记录是 500�w 条。建立索引后,检索的记录也就减到 100w-200w 条。
这种优化,并没有数量级上的优化效果,还需要占用大量空间,这种“优化”一般认为是没有意义的。
对于值为 bool 型或 enum� 型的字段,通常不建立索引。
建立索引的两种原理
索引到数据映射一般有两种实现方式,一种是 hash�,这个没有排序的能力,另一种是� B 树或 B+ 树,这个有排序的能力。
如何快速从索引中检索相应的值
通常情况下,会选择 B 树或者 B+ 树实现索引,以获得排序的能力。
从 B+ 树的数据结构来看,�解决 �Q2� 这个问题,例如要检索上个月购物记录,只要 logN 的时间就能查询到相应的记录。
�并且从上图可以看到,B+ 树的各个节点之间,都通过指针进行连接,从当前指向的数据出发,向后查找数据是非常方便和快速的。能完美的实现上拉加载更多这种需求。
应该如何建立复合索引
如果对本文开头的表,建立一个复合索引,同时查询购买时间、购物折扣等,可以�这样建立复合索引。
{ productid(升序),promotions(升序), dealdate(降序)}
这里,虽然对 productid 进行排序没有现实意义,但是在计算机看来,依然是可以排序的。
在建立� B+ 树时,先比较 productid,再比较 promotions,最后比较� dealdate,并且按照比较结果�生成一颗 B+ 树。
那么查找的时候,如果检索的内容是:{productid},索引是有效的。
如果�检索的内容是:{ productid(升序)、dealdate(降序)} 那么索引也是有效的,比较的时候,promotions 不比较(永远==)就可以了。
如果检索的内容是:{ productid(升序)、 dealdate(降序)、promotions(升序)} 这时候,索引就无效了。因为此时的比较条件,与生存索引时的比较条件不一致(生成索引时,productid 相同的情况下,比较 promotions 的大小,此时 productid 相同的情况下,比较 dealdate 的大小。)
如果检索的内容是:{ productid(升序)、dealdate(升序)} 索引也是无效的。此时比较的条件,也与索引生成时不一致。
从上文就可以看出,建立数据库索引时,就要考虑检索条件才行