第一个程序:
use redb::{Database, TableDefinition, ReadableDatabase};
const TABLE: TableDefinition<&str, u64> = TableDefinition::new("my_data");
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1) 创建或打开数据库文件
let db = Database::create("target/demo.redb")?;
// 2) 写事务
let write_txn = db.begin_write()?;
{
let mut table = write_txn.open_table(TABLE)?;
table.insert("alice", 1)?;
table.insert("bob", 2)?;
}
write_txn.commit()?;
// 3) 读事务
let read_txn = db.begin_read()?; // 来自 ReadableDatabase trait
let table = read_txn.open_table(TABLE)?;
if let Some(v) = table.get("alice")? {
println!("alice = {}", v.value());
}
for item in table.range("a"..="z")? {
let (k, v) = item?;
println!("{} -> {}", k.value(), v.value());
}
Ok(())
}
输出:
alice = 1
alice -> 1
bob -> 2
redb是一个单写入多读取的数据库。用官方文档的话说:
Multiple reads may be performed concurrently, with each other, and with writes. Only a single write may be in progress at a time.
用Casey Rodarmor的话说:
(Result type vs mmap)
Nope, unfortunately not. The issue is that if the db file is being concurrently modified by another process, that would introduce undefined behavior.redbcannot detect this, so it must be unsafe, indicating that the caller must ensure that the db is not concurrently modified.
因此,程序员首先得确保同一时间仅开启一个写入线程。
当然,redb本身也做了一定得保证,比如在实验中至少发现以下两点:
1、Database::create会对于一个demo.redb进行上锁,再次对其运行Database::create则会报错:
Error: DatabaseAlreadyOpen
2、多个线程begin_write试图同时写时,会阻塞。以下面程序为例。
use redb::{Database, TableDefinition};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
const TABLE: TableDefinition<&str, u64> = TableDefinition::new("my_data");
fn main() -> Result<(), Box<dyn std::error::Error>> {
std::fs::create_dir_all("target")?;
let db = Arc::new(Database::create(r"D:\git\redb\target\demo.redb")?);
// 第一个写事务
let db1 = Arc::clone(&db);
let handle1 = thread::spawn(move || {
let write_txn = db1.begin_write().expect("第一个写事务失败");
println!("[线程1] 第一个写事务开始,睡 5 秒...");
thread::sleep(Duration::from_secs(5));
{
let mut table = write_txn.open_table(TABLE).unwrap();
table.insert("alice", 1).unwrap();
}
write_txn.commit().unwrap();
println!("[线程1] 第一个写事务提交完成");
});
// 第二个写事务
let db2 = Arc::clone(&db);
let handle2 = thread::spawn(move || {
println!("[线程2] 尝试开始第二个写事务...");
let txn = db2.begin_write().expect("[线程2] 写事务失败");
println!("[线程2] 第二个写事务获得锁,开始写入");
{
let mut table = txn.open_table(TABLE).unwrap();
table.insert("bob", 2).unwrap();
}
txn.commit().unwrap();
println!("[线程2] 第二个写事务提交完成");
});
handle1.join().unwrap();
handle2.join().unwrap();
Ok(())
}
结果如下
[线程1] 第一个写事务开始,睡 5 秒…
[线程2] 尝试开始第二个写事务…
[线程1] 第一个写事务提交完成
[线程2] 第二个写事务获得锁,开始写入
[线程2] 第二个写事务提交完成
至于上锁和阻塞是如何实现的,我们下节课继续
1490

被折叠的 条评论
为什么被折叠?



