数据检索的玄铁剑——索引

2,978 阅读7分钟

从搬运 DTO 到 CRUD

在如今的开发模式下,服务端程序员离原始数据越来越远,和农夫山泉一样,他们不生产数据,他们只是 DTO 的搬运工。从各种 service 中获取数据,再使用 Lambda 进行拆分组装成为了他们的日常工作。

然而,随着各家大厂都开始“降本增效”,DTO 的搬运工越来越不具备竞争力,“技多不压身”变成了下一阶段的 OKR,于是「CRUD 工程师」便“应运而生”了。

本文的内容便是围绕着 CRUD 中的 R(ead)展开的。

数据检索的玄铁剑——索引

在现实生活中,如果你想使用新华字典查询一个字,在没有背下来具体页码的情况下,第一步多半是打开目录,根据拼音首字母快速的锁定目标数据所在的位置范围。如下图 👇

索引究竟是什么?

百度百科是从数据库的角度出发给出了一个索引的定义,维基百科也并没有为 CS 中的索引做一个概述,而是细分了多个领域来介绍 👉en.wikipedia.org/wiki/Index

image-3

本质上,索引是一种用于提高数据检索效率的技术,它可以是一种复杂的数据结构(Hash,B Tree……),也可以就是一个简单的下标。

为了更好的理解索引,先看一下没有索引的查询是什么样的?

没有索引的查询

上班路上,你和一个长发姐姐擦肩而过,来到公司,惊喜的发现她竟然也在这栋楼上班,此时电梯停在了 3 楼,小姐姐出去之后你便继续乘到了 6 楼,虽然你一直写着代码,但此时你的心早已飞去了三楼。

OK,那么问题来了,如果你想再见到那个长发姐姐,第一想法是什么?一定不是发表白墙吧。

在纠结了半天之后,最后你还是选择了最原始但也是最简单的办法,去三楼的工位一个个找。

你一边遍历着所有工位上的人,一边幻想着等再见面时的场景。终于皇天不负苦心人,在你离她还有六个工位的时候,你见到了她。就在你以为终于能发出“一起喝咖啡”的邀约时,一位靓仔从你的后面“瞬移”到她面前,然后说出了那句“有时间一起喝咖啡吗?”。

事了拂尘去,靓仔最后回头看了你一眼,然后说到:“小伙子,爷有索引”。

微观视角的索引——什么才是有意义的索引

上面这个例子就是一个很典型的场景,在没有索引的情况下,查询就变得简单粗暴——全表扫描。查询耗时完全由数据量决定,海量数据的查询基本无法满足需求。 由于遍历的时间复杂度是 O(n),那么为了让索引变得有意义,其时间复杂度必定是小于 O(n)。

常刷算法题的小伙伴们都知道,经常出现查找的两类数据结构就是数组和树,其实也对应着两种最常见的索引。

  • 哈希索引:复杂度为 O(1)
  • 树索引:复杂度为 O(log n)

哈希索引原理是根据属性组合直接通过哈希函数计算出结果数据的地址,一般来说更快(包括建索引的效率和查询效率),具体性能依赖于数据集和哈希函数的匹配程度。

树索引原理是基于属性组合建立树再根据二分查找定位数据,虽然建索引和查找速度都慢一些,但优势是可以支持范围查询和 front-n 属性匹配(前缀匹配)的查询。其中 front-n 属性的查询意思是,属性组合中的前 1 到前 n 个属性组成的子组合的查找。例如属性组合是 A-B-C,那么树索引可以支持 A、A-B、A-B-C 三个属性组合的查找。

基于这两类数据结构,可以延伸出非常非常多具体类型的索引,这里就不过过阐述了。接下来我们把格局打开,来看看宏观视角下的索引是如何运用的。

宏观视角的索引——全局索引/本地索引

独立于源数据之外,索引的存储自然也是要保存在另一张表中。提供主键查询的表称为主表,满足绝大多数的业务查询场景。提供非主键查询的表称为二级索引表,主表是一级索引表。

随着项目的演进,单点数据库肯定无法满足生产的需要,因此主表数据与索引数据在物理上是否在同一节点就十分重要了。显然,这是分布式存储/数据库才有的问题。

本地索引,即索引数据和被索引的数据在一起。

全局索引,则不考虑索引数据与被索引数据的分布关系,索引数据按索引表的主键列独立组织。

什么场景适合全局索引

由于索引表与主表是相互独立的,主表在写入时生成的索引可能需要跨节点写入索引表,因此写入延迟会有影响。但是索引表管理方便,能充分利用已有的分布式能力。因此全局索引适合读多写少的场景

什么场景适合本地索引

索引表与主表是融合在一起的,索引是个单独的列族,与主表列族在同一个 region 内部。因此适合读少写多的场景。通常会应用在已知主键约束的前提下,还需要对非主键查询的场景。

索引的代价

唯物辩证法告诉我们,任何事物都是对立统一的。既然索引能提高检索效率,就一定会付出一些代价。

我们刚才提到,独立于源数据之外,索引需要额外的空间来存储,也需要定期维护。每当有记录在表中增减或索引列被修改时,索引本身也会被修改。这意味着每条记录的写操作将为此多付出磁盘 I/O。

此外,因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。

这就和上文提到的那个会瞬移的小哥一样,虽然他通过关系网找到了那个女生的工位,但中间欠下的人情都是一杯杯咖啡换来的。

最后

在这篇文章中,我们聊了聊索引的相关知识,作为数据检索的玄铁剑,我们虽然没有聚焦于某些具体的索引,但是以上帝视角重新审视了索引的微观存在与宏观运用。希望这篇文章可以起到抛砖引玉的作用,引发更多讨论,一起学习、共同成长。

最后说点题外话,最近在内网看到了晓斌老师的那篇《论好文章和烂文章》,突然对号入座了起来,想到自己最近几年写的文章,好像都归属这一类。可能这也是这个号一直小众的原因吧。

如果要给一篇文章打个绩效,那 3.75 的文章应该是有较多自己的思考,观点清晰,对阅读者有很大帮助的,相比之下,3.5 的文章就应该是有自己的思考,观点清晰,对阅读者有帮助。而剩下那些只会罗列素材,没有总结提炼的,教科书笔记般的文章自然就是 3.25 了。

未来肯定是争取产出更多 375 的文章,再不济也是 3.5 的,偏笔记教程类的应该就不会再往公众号发了,最多在自己的博客里专门加一类笔记的 tag。如果你觉得我的文章对你有所帮助,不妨点个关注支持一下原创内容。