这篇文章是levelDB官方文档的译文,原文地址:
LevelDB library documentation这篇文章主要讲leveldb接口使用和注意事项。
leveldb是一个持久型的key-value数据库。key,value可以是任意的字节数组,key之间是有序的。key的比较函数可以由用户指定。
1. 打开数据库
leveldb使用文件系统目录名作为name,并把数据库所有内容都存储在这个目录中。这是个打开数据库,并且指定如果数据库不存在就新建的例子:
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;
2. 返回值状态Status
你也许已经注意到上面的
leveldb::Status type
. 这种类型是leveldb大多数函数的返回值,函数的返回值有可能是error。可以通过判断result是不是ok来判断:
leveldb::Status s = ...;
if (!s.ok()) cerr << s.ToString() << endl;
3. 关闭数据库
当数据库操作完成,关闭数据库只需要删除数据库对象:
... open the db as described above ...
... do something with db ...
delete db;
4. 读和写
数据库提供
Put, Delete, and 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);
5. 原子性更新
如果上面的代码中,再给key2赋值之后,删除key1之前,进程退出了,那么key1 and key2就会有相同的值。这种情况可以使用
WriteBatch class
来原子性的进行一系列的更新:
...
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是一系列对数据库的更新操作,并且这些批量操作之间有一定的顺序性。注意到我们虽然在给key2赋值之前删除,使用writebtch最终并不会错误的造成vaue丢失。
撇开writebatch带来的原子性优势,writebatch也能通过把多个更新放在一个批量操里面来加速操作。
6. 同步写
通常情况下,所有的leveldb写操作都是异步的:当leveldb把写操作交个操作系统之后就返回。从操作系统内存到硬盘等持久性存储是异步的。如果在写的时候打开同步写选项,那么只有当数据持久化到硬盘之后才会返回。(On Posix systems, this is implemented by calling either
fsync(...)
or
fdatasync(...)
or
msync(..., MS_SYNC)
before the write operation returns.)
leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
异步写通常比同步写快1000倍以上。异步写的不足就是当机器宕机时会丢失最后更新的数据。写进程的异常退出并不会造成数据的丢失。通常情况下异步写能够被妥善的处理。例如,当你在网数据库写大量的数据时,在机器宕机之后能通过重新写一次数据来修复。混合使用同步和异步也是可以的。例如每N次写做一次同步。当机器宕机的时候,只需要重新写最后一次同步写之后的数据。同步写一个新增一个标记来记录上一次同步写的位置。WriteBatch是一个异步写。一个WriteBatch内部的多个更新操作放在一起也可以使用同步写操作,(i.e.,
write_options.sync
is set to true). 可以通过批量操作降低同步写的消耗。
7. 并发
一个数据库每次只能被一个进程打开。leveldb为了防止误操作需要一个lock。在一个进程内部,同一个
leveldb::DB
对象可以在这个进程的多个并发线程之间安全的共享。 例如,不同的线程可以写,获取指针,或者读取相同的数据库,而不需要额外的同步操作,因为leveldb自动做了请求的同步。然而,其他的对象,例如迭代器或者WriteBatch,需要外部的同步操作。如果两个线程共享同一个这样的对象,那么他们必须用自己的lock protocal对数据库操作进行保护。这在公共的header文件里有更详细的内容。
8. 迭代器
下面的例子说明了如何输出数据库的所有key-value对:
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());
delete it;
处理[start,limit)范围内的key:
for (it->Seek(start);
it->Valid() && it->key().ToString() < limit;
it->Next()) {
...
}
逆序处理:(逆序会比顺序慢一些)
for (it->SeekToLast(); it->Valid(); it->Prev()) {
...
}
9. Snapshots快照
快照在整个key-value存储状态上提供了一个持久性的只读视图。非空的
ReadOptions::snapshot
提供了一个针对db特定状态的只读视图。如果
ReadOptions::snapshot
是NULL,那么读操作是在对当前数据库状态的隐式视图上的进行的。使用
DB::GetSnapshot()
方法创建Snapshots:
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
接口来释放,这会消除为了维持快照的状态多与操作。
10. Slice分片
上面代码中
it->key()
and
it->value()
调用的返回值都是
leveldb::Slice
类型的实例,slice是一个包含长度和一个纸箱字节数组的简单结构体。因为我们不需要每次都复制很多的keys和values,所以返回Slice比返回std::string是一个更好的选择。另外,level-db不返回以null结尾的c类型的字符串,是因为leveldb允许key和value中包含
'