1 最简单的数据库
#!/bin/bash
db_set () {
echo "$1,$2" >> database
}
db_get () {
grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}
最简单的数据库,日志结构,写很快,读很慢。可以看出,对于日志结构的数据库,要在读操作上做优化。
为了加快读,可以添加索引。但是索引会拖慢写操作,因此只给需要频繁读的字段添加索引。
2 哈希索引
最容易想到的一种索引策略就是哈希索引。
数据以追加写的方式存储在文件中,内存里保留一个哈希表,key 为索引字段的值,value 为偏移量,如下图所示。该方法适用于键值更新频繁的场景,并且键的总量放在内存中可以hold得住。
Bitcask 就是基于哈希索引的,详见 Bitcask 存储模型
压缩、分段、合并
以上讨论的数据库都是追加日志的,这就需要考虑在日志文件过大时,对文件进行压缩、分段、合并等操作。
数据库的历史指令可能重复对一个键进行操作,显然只需要保留每个键的最新更新就行,这就需要对数据库文件进行分段压缩。在压缩时,段文件体积减小,因此可以将多个段文件合并成一个。
追加日志较之更新文件:
- 顺序写入,写速度快
- 可能会有多个段文件,读速度慢
哈希索引局限性在于:
- 哈希表必须能放进内存
- 范围查询效率很低
下面介绍一种没有这些局限的索引结构。
3 SSTable & LSM 树
对键进行排序,SSTable(Sorted String Table)
优势
- 合并压缩过程很高效,用类似归并排序的方法
- 不用在内存中保存所有键,例如可以间隔1000个数据写入一下哈希表,查询时在这1000个数据中遍历
构建和维护 SSTable
- 在内存中维护一个有序的数据结构(内存表)
- 大于某个阈值时将内存表写入段文件,也就是 SSTable
- 读取时,先在内存中找,找不到再依次从最新的 SSTable 中找
- 为了防止数据库崩溃丢失内存表中的数据,可以将每次写入写到一个追加文件中,在内存表写入到 SSTable 时删除追加文件
LSM树,保存一系列在后台合并的SSTables,以上算法就是 LSM 树的核心流程
性能优化
查找不存在的键需要遍历所有段文件,可以用布隆过滤器优化。
(bloom filter 若判断键不存在,则一定不存在,若判断键存在,则可能不存在)
4 B 树
将数据库分成若干页面,并通过类似指针的方式允许页面之间的引用,
读取
写入,空间不够时需要拆分页面,B树生长
5 比较 B 树和 LSM 树
-
LSM 树写更快,B 树读更快
B 树需要写两次,一次写入预写式日志(仅追加文件),一次写入页面,写页面时可能分页,进一步降低速度。
LSM 树读需要遍历多个 SSTable -
LSM 树对存储空间的利用率更高
SSTable 较紧凑,B 树有碎片 -
B 树结构提供事务语义更方便
B 树每个键只存在于索引中的一个位置,而日志结构化的存储引擎可能在不同的段中有相同键的多个副本
B 树立刻写入磁盘,LSM 树在内存保存一段时间,再写入磁盘
6 其它索引结构
以上讨论的是主键索引,可以在一个表上建立多个二级索引
将值存储在索引中
聚集索引(clustered index) 将数据行存储在索引中,非聚集索引(nonclustered index) 索引存储对数据行的引用,或者存储主键值
- 存储主键值,需要回表操作,MySQL InnoDB就是这样做的
- 存储位置,数据源存储在堆文件中。但是这涉及到一个问题,update 操作,新值比旧值占用更多空间,该数据可能需要移动到堆文件中一个新的位置,这种情况下,要么所有指向该数据的索引都更新,要么在旧位置留下一个指向新位置的指针。
(以下查自网络)
聚集索引适用场景:
- 大数目的不同值
- 范围查询
非聚集索引适用场景:
- 小数目的不同值
- 频繁更新的列
折中做法,covering index 覆盖索引
create index new_index on Table(A, B)
为字段 A 建立索引,根据字段 A 找到的叶节点,也会存储字段 B 的值
多列索引
需要查询多个列的数据,用到多列索引
- 连接索引 concatenated index
将一列的值追加到另一列值的后面,组合成一个新的键,例如 姓-名 组合成 姓名 - 多维索引 multi-dimensional index
例如 R 树存储地理位置(详见 从B树、B+树、B*树谈到R 树 )
7 事务 or 分析
数据库中的数据用于在线事务处理(OLTP)或者在线分析处理(OLAP)
需求不同,所以需要分开,其中用于数据分析的数据库称之为数据仓库(data warehouse)
列存储
设想场景,一个表有很多列,但是数据仓库的业务查询通常只需要用到其中两三列进行筛选,假如表是按行布局的,需要加载所有行数据,并依次过滤
如果按列存储数据,相当于每一列都添加索引,不仅如此,每一列都可以范围读,且不用跨页读,读取速度快
列存储很适合压缩
使用 bitmap 来压缩,对于查询
WHERE product_sk IN(30,68,69)
直接按位或运算
WHERE product_sk = 31 AND store_sk = 3
加载位图按位与运算
来源:CSDN
作者:fiverwyp
链接:https://blog.csdn.net/u014645632/article/details/104728088