0101: KV 接口
内存 vs. 硬盘
被叫做 database 的软件各式各样。有纯粹内存型的,例如 Redis、Memcached。有基于硬盘的,例如 MySQL、SQLite。
显然,内存型数据库受到内存大小限制,所以传统意义上的数据库都是基于硬盘的。但即使是基于硬盘的数据库,很多都包含一些内存数据结构。所以不同类型的数据库不是完全没有关系。我们可以从一个内存型的 KV 开始,通过逐步增加代码来完成项目。
数据库是一种对数据结构的应用;数据存储在内存还是在硬盘,原理上没有区别,可是在实现上会碰到许多问题。这些话题你基本不会在书本中遇到,但可以在项目中学习。
Key-value vs. table
数据库按照提供的接口,可分为 key-value (KV),关系型,其他特殊类型。 KV 就是编程语言里的 map、dict,一般有 get、set、del 这些操作。关系型就是一些表格(行、列),一般用 SQL 来操作。
从功能上看,关系型能做到的事情似乎更多。然而,更复杂关系型数据库,底层其实是更简单 KV,这个 KV 一般叫做“存储引擎”。例如 LevelDB、RocksDB,他们既可以单独作为 KV,又有基于他们的 SQL 数据库实现。所以实现数据库要从 KV 开始。
OLAP vs. OLTP
数据库可以按照用途分为2类:Online Analytical Processing 和 Online Transaction Processing。这些名词只是一些名字,其中的词语 analytical、transaction 并没有具体的含义。 OLAP 偏向于分析大量数据,OLTP 偏向于借助索引实时返回结果。举个例子:
- OLAP: 统计活跃用户数量,小王随手一条SQL,将符合条件的用户
COUNT()了一遍。 - OLTP: App 上实时显示用户数量,由于用户太多,额外维护了一个计数器来记录。
OLTP 场景要做到实时结果,每次操作消耗的资源(CPU、IO、内存)是有上限的。如果你对数据结构有概念,这个上限通常是 O(log N)。所谓“索引”就是用额外的信息来满足实时查询,空间换时间。应用上广义的索引,不一定对应到数据库里索引这个功能。就像上面这个例子,应用自己维护了额外的索引,这是因为数据库里没有对“记数”来索引的功能。
OLAP 场景由于消耗的资源可能很大,往往和 OLTP 分开储存。传统的的数据库如 MySQL、PG 是 OLTP 类型。而在“大数据”概念兴起后流行起了专门做 OLAP,效率更高的数据库。
同样叫做“关系型数据库”,OLAP 大都按“列”来储存,而 OLTP 按“行”来储存。他们的底层技术不相通,侧重的功能、场景也不同。这个项目只考虑 OLTP。学习时不要混淆。
内存 KV
内存 KV 从最简单的 map 开始:
type KV struct {
mem map[string][]byte
}
func (kv *KV) Open() error {
kv.mem = map[string][]byte{} // empty
return nil
}
func (kv *KV) Close() error { return nil }
func (kv *KV) Get(key []byte) (val []byte, ok bool, err error)
func (kv *KV) Set(key []byte, val []byte) (updated bool, err error)
func (kv *KV) Del(key []byte) (deleted bool, err error)- Key 跟 value 都是
[]byte,可以承载任意二进制数据。 - 因为 Golang map 的 key 不能是
[]byte,所以用了 string。 - 以后会有硬盘 IO,所以这些接口预留了
error返回。 Set和Del要返回数据库状态是否变化。
进入 db_project/0101 目录。实现 Get, Set, Del 函数。运行测试用例:
go test .ACID
原子性(atomicity),一致性(consistency),隔离性(isolation),持久性(durability),又叫做 ACID。这4个词语经常用来描述数据库,仿佛是数据库的4种特性。你可能会觉得这些概念很重要,但又搞不明白具体是怎么回事,这并不是因为你没有理解,而是因为这些词语只是一些模糊的概念,并没有明确的定义,甚至有许多互不相关的含义。
原子性,字面意思是不可分割。比如文件写入n个字节,这个过程中:
- 如果有并发的读取,可能会读到写入一半的数据。
- 如果中途断电,恢复后结果可能只有一半的数据。
这两种情况都可以描述为没有原子性,数据库要解决这些问题。然而一个是在讲并发,另一个是在讲持久化,是不相干的两个问题。
一致性,用来描述数据库内部的逻辑、业务逻辑、分布式系统,连模糊的定义都没有。
隔离性,指一个“事务”内是否会受到其他并发事务影响。
持久性,指数据库返回成功后,可以依赖数据不会消失。具体到实现上,这不是一个可以单独考虑的特性,比如至少要解决写入的原子性。
数据库的行为很复杂,ACID 并不能充分描述,实际使用时要学习具体数据库的具体行为。而学习数据库,不如自己实现一个数据库。这样可以直接接触到数据库要解决的具体问题,以及解决方案,胜过各种无聊的名词解释。
您正在阅读免费版教程,从第4章起只有简单的指引,适合爱好挑战和自学的读者。
可以购买有详细指导+背景知识的完整版。