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类:
- OLAP: online analytical processing
- OLTP: online transaction processing
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 并不能充分描述,实际使用时要学习具体数据库的具体行为。而学习数据库,不如自己实现一个数据库。这样可以直接接触到数据库要解决的具体问题,以及解决方案,胜过各种无聊的名词解释。