01.无索引

简单的Bash数据库

数据存取

世界上最简单的数据库可以用两个Bash函数实现:

#!/bin/bash
db_set () {
	echo "$1,$2" >> database
}

db_get () {
	grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}

这两个函数实现了键值存储的功能。执行db_set key value,会将 键(key)和值(value)存储在数据库中。键和值(几乎)可以是你喜欢的任何东西,例如,值可以是JSON文档。然后调用db_get key,查找与该键关联的最新值并将其返回。

$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}' $

$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'

$ db_get 42
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}

底层的存储格式非常简单:一个文本文件,每行包含一条逗号分隔的键值对(忽略转义问题的话,大致与CSV文件类似。每次对db_set的调用都会向文件末尾追加记录,所以更新键的时候旧版本的值不会被覆盖。我们使用了tail -n 1,因而查找最新值的时候,需要找到文件中键最后一次出现的位置。

$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}'

$ db_get 42
{"name":"San Francisco","attractions":["Exploratorium"]}

$ cat database
123456,{"name":"London","attractions":["Big Ben","London Eye"]}
42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
42,{"name":"San Francisco","attractions":["Exploratorium"]}

db_set函数对于极其简单的场景其实有非常好的性能,因为在文件尾部追加写入通常是非常高效的。与db_set做的事情类似,许多数据库在内部使用了日志(log,也就是一个 仅追加(append-only)的数据文件。真正的数据库有更多的问题需要处理(如并发控制,回收磁盘空间以避免日志无限增长,处理错误与部分写入的记录,但基本原理是一样的。

索引

如果这个数据库中有着大量记录,则这个db_get函数的性能会非常糟糕。每次你想查找一个键时,db_get必须从头到尾扫描整个数据库文件来查找键的出现。用算法的语言来说,查找的开销是O(n):如果数据库记录数量n翻了一倍,查找时间也要翻一倍。这就不好了。

为了高效查找数据库中特定键的值,我们需要一个数据结构:索引(index。索引背后的大致思想是,保存一些额外的元数据作为路标,帮助你找到想要的数据。如果您想在同一份数据中以几种不同的方式进行搜索,那么你也许需要不同的索引,建在数据的不同部分上。

索引是从主数据衍生的附加(additional)结构。许多数据库允许添加与删除索引,这不会影响数据的内容,它只影响查询的性能。维护额外的结构会产生开销,特别是在写入时。写入性能很难超过简单地追加写入文件,因为追加写入是最简单的写入操作。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。

这是存储系统中一个重要的权衡:精心选择的索引加快了读查询的速度,但是每个索引都会拖慢写入速度。因为这个原因,数据库默认并不会索引所有的内容,而需要你(程序员或DBA)通过对应用查询模式的了解来手动选择索引。你可以选择能为应用带来最大收益,同时又不会引入超出必要开销的索引。

下一页