在实际的软件开发中,其实它们的本质都可以抽象为“对数据的存储和计算”。对应到数据结构和算法中,那“存储”需要的就是数据结构,“计算”需要的就是算法。
“如何节省存储空间、如何提高数据增删改查的执行效率”,这样的问题是设计的重点。索引设计得好坏,直接决定了一个系统的性能是否优秀。
索引这个概念可以类比书籍的目录,如果没有目录查找某个知识点的时候,就要一页一页翻。通过目录则就可以快速定位相关知识点的页数,查找的速度也会有质的提高。
对于系统设计需求,一般可以从功能性需求和非功能性需求两方面来分析。
数据是格式化数据还是非格式化数据?
结构化数据,比如MySQL 中的数据;
非结构化数据,比如搜索引擎中网页。
对于非结构化数据,一般需要做预处理,提取出查询关键词,对关键词构建索引。
数据是静态数据还是动态数据?
对于静态数据只需要考虑查询效率就可以了,这样索引的构建就相对简单些。
但大部分情况下都是对动态数据构建索引,在原始数据更新的同时还需要动态地更新索引。
支持动态数据集合的索引,设计起来相对也要更加复杂些。
索引存储在内存还是硬盘?
存储在内存的查询速度肯定比存储在磁盘中的高,但因为内存有限,有时就不得不将索引存储在磁盘中。实际上,还可以一部分存储在内存,一部分存储在磁盘,兼顾内存消耗和查询效率。
单值查找还是区间查找?
单值查找就是查找关键词等于某个值的数据。
区间查找,就是查找关键词处于某个区间值的所有数据。
单关键词查找还是多关键词组合查找?
单关键词的查找,索引构建起来相对简单些。
对于多关键词查询来说:
对于像 MySQL 这种结构化数据的查询需求,可以针对多个关键词的组合,建立索引;
对于像搜索引擎这样的非结构数据的查询需求,可以针对单个关键词构建索引,然后通过集合操作,比如求并集、求交集等,计算出多个关键词组合的查询结果。
不管是存储在内存中还是磁盘中,索引对存储空间的消耗不能过大。
如果存储在内存中,索引对占用存储空间的限制就会非常苛刻。毕竟内存空间非常有限,一个中间件启动后就占用几个 GB 的内存,开发者显然是无法接受的。
如果存储在硬盘中,那索引对占用存储空间的限制,稍微会放宽一些。但也不能掉以轻心。因为有时候,索引对存储空间的消耗会超过原始数据。
在考虑索引查询效率的同时,还要考虑索引的维护成本。
索引的目的是提高查询效率,但基于动态数据集合构建的索引,还要考虑到,索引的维护成本。
对原始数据动态增删改的同时,也需要动态的更新索引。而索引的更新势必会影响到增删改操作的性能。
常用来构建索引的数据结构,有散列表、红黑树、跳表、B+ 树。除此之外,位图、布隆过滤器可以作为辅助索引,有序数组可以用来对静态数据构建索引。
散列表增删改查操作的时间复杂度是 O(1)。一些键值数据库,比如 Redis、Memcache,就是使用散列表来构建索引的。这类索引,一般都构建在内存中。
红黑树作为一种常用的平衡二叉查找树,数据插入、删除、查找的时间复杂度是 O(logn),也非常适合用来构建内存索引。Ext 文件系统中,对磁盘块的索引,用的就是红黑树。
B+ 树比起红黑树来说,更加适合构建存储在磁盘中的索引。B+ 树是一个多叉树,所以,对相同个数的数据构建索引,B+ 树的高度要低于红黑树。当借助索引查询数据的时候,读取 B+ 树索引,需要的磁盘 IO 次数非常更少。所以,大部分关系型数据库的索引,比如 MySQL、Oracle,都是用 B+ 树来实现的。
跳表也支持快速添加、删除、查找数据,可以通过灵活调整索引结点个数和数据个数之间的比例,平衡索引对内存的消耗及其查询效率。Redis 中的有序集合是用跳表来构建的。
布隆过滤器有一定的判错率,但内存占用非常少。针对数据构建一个布隆过滤器后,查询时可以先通过布隆过滤器,判定是否存在。被布隆过滤器判定数据不存在,就没有必要读取磁盘中的索引了。所以,布隆过滤器可以作为 辅助索引,加速查找效率。
有序数组可以作为静态数据的索引,利用二分查找算法可以快速查找数据。