Leveldb 使用说明文档
原作者:Jeff Dean, Sanjay Ghemawat
翻译:乌合之众solym@sohu.com
英文原文地址https://rawgit.com/google/leveldb/master/doc/index.html
leveldb库提供持久性键值存储。 键和值可以是任意字节数组。 根据用户指定的比较函数,在键值存储器内对键进行排序。
打开一个数据库
leveldb打开数据库需要指定一个文件系统目录。 数据库的所有内容都存储在此目录中。 以下示例显示如何打开数据库,如有必要,可创建该数据库:
#include <cassert>
#include "leveldb/db.h"
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
assert(status.ok());
...
如果要在数据库 已存在时返回错误 ,请在leveldb::DB::Open
调用之前添加以下行:
options.error_if_exists = true;
状态 Status
你也许已经注意到了上面的leveldb::Status
类型。leveldb中可能遇到错误的大多数函数都返回此类型的值。 您可以检查这样的结果是否正确,并打印相关的错误消息:
leveldb::Status s = ...;
if (!s.ok()) cerr << s.ToString() << endl;
关闭数据库
使用完数据库后,只需删除数据库对象。 例:
... open the db as described above ...
... do something with db ...
delete db;
读和写 Reads And Writes
数据库提供Put
,Delete
和Get
方法来修改/查询数据库。 例如,以下代码将key1
下存储的值
移动到key2
。
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);
if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);
原子更新 Atomic Updates
注意,如果进程在Put Key2 之后、Delete key1之前结束(挂了),会使得value被同时保存在多个key下。使用WirteBatch
类进行原子更新一组操作,可以避免这些问题:
#include "leveldb/write_batch.h"
...
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
if (s.ok()) {
leveldb::WriteBatch batch;
batch.Delete(key1);
batch.Put(key2, value);
s = db->Write(leveldb::WriteOptions(), &batch);
}
WriteBatch
保存对数据库进行的一系列编辑,该批次内的这些修改才能被应用。 注意,我们在Put之前调用Delete,因此如果key1与key2相同,我们不会错误的完全丢弃该值。
除了它的原子性好处,WriteBatch
也可以用于通过将大量的单个操作
放入同一批次来加速批量更新。
异步写 Synchronous Writes
默认情况下,每次写入leveldb都是异步
的:将写入从进程推送到操作系统后立即返回。从操作系统内存到底层持久存储的传输是异步的。 对于特定的写操作,可以打开同步标志
,以使写操作不会返回,直到正在写入的数据真正写入永久存储器。 (在Posix系统上,这是通过在写操作返回之前调用fsync
(...)或fdatasync
(...)或msync
(...,MS_SYNC)实现的。
leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
异步写操作通常比同步写操作快一千倍。 异步写入的缺点是,机器的崩溃可能导致最后几个更新丢失。 请注意,只是写入进程(即不是机器重新启动)的崩溃不会导致任何损失 ,因为即使sync
为false
,更新从进程内存推送到操作系统在之后即认为完成。
通常可以安全地使用异步写入。 例如,将大量数据加载到数据库中时,可以通过在崩溃后重新启动批量加载来处理丢失的更新。 混合方案也是可能的,其中每第N次写入是同步的,并且在崩溃的情况下,批量加载刚好在由前一次运行完成的最后同步写入之后重新启动。 (同步写入可以更新描述崩溃时重新启动位置的标记。)
WriteBatch提供了异步写入的替代方法。 多个更新可以放置在同一WriteBatch
中,并使用同步写入(即,write_options.sync
设置为true
)一起应用。 同步写入的额外成本将在批次中的所有写入之间摊销。
并发 Concurrency
数据库只能由一个进程一次打开。leveldb实现从操作系统获取锁
以防止误用。 在单个进程中,同一个leveldb::DB
对象可以安全地由多个并发线程共享。 即,不同的线程可以写入或获取迭代器或在没有任何外部同步的情况下在同一数据库上调用Get
(leveldb实现将自动执行所需的同步)。
但是其他对象(如Iterator
和WriteBatch
)可能需要外部同步。如果两个线程共享这样的对象,它们必须使用自己的锁定协议保护对它的访问。 更多详细信息在公共头文件中可用。
迭代器 Iteration
以下示例演示如何在数据库中打印所有 键-值对 。
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
cout << it->key().ToString() << ": " << it->value().ToString() << endl;
}
assert(it->status().ok()); // Check for any errors found during the scan
delete it;
以下显示了如何仅处理范围[start,limit)中的键:
for (it->Seek(start);
it->Valid() && it->key().ToString() < limit;
it->Next()) {
...
}
您还可以按相反的顺序处理条目。 (注意:反向迭代可能比前向迭代慢一些。)
for (it->SeekToLast(); it->Valid(); it->Prev()) {
...
}
快照 Snapshots
快照在键值存储的整个状态上提供一致的只读视图。ReadOptions::snapshot
可能是非NULL
的,表示read
操作应该在某个特定版本的DB状态。 如果ReadOptions::snapshot
为NULL,则读取操作将对当前状态的隐式快照
进行操作
快照由DB::GetSnapshot() 方法创建:
leveldb::ReadOptions options;
options.snapshot = db->GetSnapshot();
... apply some updates to db ...
leveldb::Iterator* iter = db->NewIterator(options);
... read using iter to view the state when the snapshot was created ...
delete iter;
db->ReleaseSnapshot(options.snapshot);
请注意,当不再需要快照时,应使用DB :: ReleaseSnapshot接口释放快照。这允许实现排除维护状态,只支持从该快照读取。
分片 Slice
上面it->key()
和it->value()
调用的返回值是leveldb::Slice
类型。Slice是一个简单的结构,包含一个长度和一个指向外部字节数组的指针。返回Slice
是比返回std::string
的更廉价替代方法,因为我们不需要复制潜在的大key
和value
。此外,leveldb方法不返回以null结束的C风格字符串,因为leveldb键和值允许包含