读写
数据库提供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
两个操作之间结束,那么这两个键将存储相同的值。因此,尽可能使用WriteBatch
类来避免这类问题:
#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
对象保存对数据库进行的一系列操作,然后在这一批次中按照顺序应用这些操作。注意:这里先进行Delete
操作,然后再进行Put
操作,是因为在key1
和key2
相同的情况下不会错误的将该值丢弃。
WriteBatch
类除了原子性的优势外,也可以用于通过将大量个体变动放置在同一批次中而加速批量更新。
同步写入 - Synchronous Writes
在默认情况下,levedb
中的每一次写操作都是异步的:它会在把写入操作从进程中推送到操作系统后返回,而从操作系统内存到底层持久化存储的传输是异步的。对于特定的写操作,是可以打开同步sync
标志使写操作一直到数据被传输到底层存储器后再返回。(在基于Posix标准的操作系统系统中,这一步是通过在写操作返回之前调用fsync(…)
或fdatasync(…)
或nsync(…, MS_SYNC)
实现的。)
leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
异步写操作一般比同步写操作快很多很多。但异步写入的缺点是,在机器宕机时有可能导致最后几步的更新丢失。请注意,如果是在写入过程中的宕机(而非重新启动),即使sync
设置为false
,更新操作也会认为已经将更新从内存中推送到了操作系统。
通常可以安全地使用异步写入。比如,当加载大量数据到数据库中时,可以通过在宕机后重新启动批量加载来处理丢失的更新。有一个可用的混合方案,将多次写入的第N次写入设置为同步的,并在宕机重启后的情况下,批量加载由前一次运行的最后一次同步写入之后重新开始。(同步写入时可以更新描述宕机后批量加载重新开始的标记。)
WriteBatch
提供一个代替异步写操作的选择:将多个更新操作放置在同一个WriteBatch
对象中然后使用同步写入一起应用(write_options.sync
设置为ture
);这时同步写入的额外成本开销将在这批次中所有的写入操作中摊销。