DDIA读书笔记 第三章 存储与检索

╄→尐↘猪︶ㄣ 提交于 2020-03-08 14:59:02
log-structed 日志结构
哈希索引
SSTable 与 LSM树
日志文件的压缩 分段 合并
page-oriented 面向页面
B树
数据库
聚集索引 非聚集索引
多列索引
数据分析
列存储

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 存储模型

压缩、分段、合并

以上讨论的数据库都是追加日志的,这就需要考虑在日志文件过大时,对文件进行压缩、分段、合并等操作。

数据库的历史指令可能重复对一个键进行操作,显然只需要保留每个键的最新更新就行,这就需要对数据库文件进行分段压缩。在压缩时,段文件体积减小,因此可以将多个段文件合并成一个。
merge

追加日志较之更新文件:

  • 顺序写入,写速度快
  • 可能会有多个段文件,读速度慢

哈希索引局限性在于:

  • 哈希表必须能放进内存
  • 范围查询效率很低

下面介绍一种没有这些局限的索引结构。

3 SSTable & LSM 树

对键进行排序,SSTable(Sorted String Table)
sstable

优势

  • 合并压缩过程很高效,用类似归并排序的方法
  • 不用在内存中保存所有键,例如可以间隔1000个数据写入一下哈希表,查询时在这1000个数据中遍历
    sstable

构建和维护 SSTable

  • 在内存中维护一个有序的数据结构(内存表)
  • 大于某个阈值时将内存表写入段文件,也就是 SSTable
  • 读取时,先在内存中找,找不到再依次从最新的 SSTable 中找
  • 为了防止数据库崩溃丢失内存表中的数据,可以将每次写入写到一个追加文件中,在内存表写入到 SSTable 时删除追加文件

LSM树,保存一系列在后台合并的SSTables,以上算法就是 LSM 树的核心流程

性能优化

查找不存在的键需要遍历所有段文件,可以用布隆过滤器优化。
(bloom filter 若判断键不存在,则一定不存在,若判断键存在,则可能不存在)

4 B 树

将数据库分成若干页面,并通过类似指针的方式允许页面之间的引用,
读取
B 树查找
写入,空间不够时需要拆分页面,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)
OLTP vs OLAP
需求不同,所以需要分开,其中用于数据分析的数据库称之为数据仓库(data warehouse)

列存储

设想场景,一个表有很多列,但是数据仓库的业务查询通常只需要用到其中两三列进行筛选,假如表是按行布局的,需要加载所有行数据,并依次过滤

如果按列存储数据,相当于每一列都添加索引,不仅如此,每一列都可以范围读,且不用跨页读,读取速度快

列存储

列存储很适合压缩
列存储压缩
使用 bitmap 来压缩,对于查询

WHERE product_sk IN306869)

直接按位或运算

WHERE product_sk = 31 AND store_sk = 3

加载位图按位与运算

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!