概览
ThetaDB
适用于对移动客户端有“高读、低写”要求的场景,它使用 B+ 树作为索引管理的基础层。
受 Go 的 BoltDB 启发,ThetaDB 使用 mmap
,依托操作系统保持内存和数据库文件的同步。ThetaDB 还实现了 shadow paging
来确保事务的原子性和持久性,防止数据丢失或数据库内部结构损坏。
安装
ThetaDB 为这些语言提供了 API:Rust
、Swift
、Kotlin
。
Rust - Cargo
将 Thetadb
添加到您的 Cargo.toml
依赖项中。
[dependencies]
thetadb = 0.0.1
Swift - 包管理器
在您的 Package.swift
中添加 ThetaDB
依赖项
.package(url: "https://github.com/TangentW/ThetaDB.git", from: "0.0.1")
然后指定 ThetaDB
作为您希望在 Target 中使用 ThetaDB 的依赖项
Swift - CocoaPods
将该库添加到您的 Podfile
pod 'ThetaDB'
然后执行
$ pod install
Java
🚧 尚待开发。
使用方法
ThetaDB 的 API 在不同语言中通常相似。
打开数据库
使用以下方法在指定路径打开数据库。如果数据库文件不存在,ThetaDB 将自动创建并初始化它。
Rust 示例
use thetadb::{Options, ThetaDB};
let path = "path/to/db.theta";
// The simplest way to open with default `Options`:
let db = ThetaDB::open(path)?;
// Open with `Options`:
let db = Options::new()
.force_sync(true)
.mempool_capacity(8)
.open(path)?;
Swift 示例
import ThetaDB
let path = "path/to/db.theta"
// The simplest way to open with default `Options`:
let db = try ThetaDB(path: path)
// Open with `Options`:
let db = try ThetaDB(
path: path,
options: .init(
forceSync: true,
mempoolCapacity: 8
)
)
ThetaDB 对文件名和扩展名没有特定要求,但建议使用 theta
作为扩展名,以便于识别。
ThetaDB 在数据库实例被销毁时将自动关闭。
获取、插入、更新、删除
Rust 示例
// Insert a new key-value pair into database.
db.put(b"foo", b"foo")?;
// Check if the database contains a given key.
assert!(db.contains(b"foo")?);
assert!(!db.contains(b"unknown")?);
// Get the value associated with a given key.
assert_eq!(
db.get(b"foo")?,
Some(b"foo".to_vec())
);
assert_eq!(
db.get(b"unknown")?,
None
);
// Update an existing value associated with a given key.
db.put(b"foo", b"bar")?;
assert_eq!(
db.get(b"foo")?,
Some(b"bar".to_vec())
);
// Delete an existing key-value pair from database.
db.delete(b"foo")?;
assert!(!db.contains(b"foo")?);
assert_eq!(
db.get(b"foo")?,
None
);
Swift 示例
// Insert a new key-value pair into database.
try db.put("foo".data(using: .utf8)!, for: "foo")
// Check if the database contains a given key.
assert(try db.contains("foo"))
assert(try !db.contains("unknown"))
// Get the value associated with a given key.
assertEq(
try db.get("foo"),
"foo".data(using: .utf8)
)
assertEq(
try db.get("unknown"),
nil
)
// Update an existing value associated with a given key.
try db.put("bar".data(using: .utf8)!, for: "foo")
assertEq(
try db.get("foo"),
"bar".data(using: .utf8)
)
// Delete an existing key-value pair from database.
try db.delete("foo")
assert(try !db.contains("foo"))
assertEq(
try db.get("foo"),
nil
)
事务
ThetaDB 有两种类型的事务:只读事务
和 读写事务
。只读事务仅允许进行只读操作,而读写事务允许修改。
ThetaDB 允许多个只读事务同时进行,但任何时刻最多只能有一个读写事务。当读写事务处于提交状态时,它将拥有对数据库的独占访问权,直到提交完成,在此期间其他尝试访问数据库的事务将会被阻塞。你可以将这种情况视为读写锁的共享访问
和独占访问
。
只读事务
Rust 示例
// Start a read-only transaction.
let tx = db.begin_tx()?;
// Then perform read-only access.
_ = tx.contains(b"foo")?;
_ = tx.get(b"foo")?;
// Or you can perform a read-only transaction using closure,
// with `view` method:
db.view(|tx| {
_ = tx.contains(b"foo")?;
_ = tx.get(b"foo")?;
Ok(())
})?;
Swift 示例
// Start a read-only transaction.
let tx = try db.beginTx()
// Then perform read-only access.
_ = try tx.contains("foo")
_ = try tx.get("foo")
// Or you can perform a read-only transaction using closure,
// with `view` method:
try db.view {
_ = try $0.contains("foo")
_ = try $0.get("foo")
}
读写事务
ThetaDB 的读写事务被设计为自动回滚,因此除非你显式调用 commit
方法,否则事务中所做的任何更改都将被丢弃。
或者,您可以使用闭包执行读写事务,如果没有发生错误,则在闭包调用后,事务将自动提交。
Rust 示例
// Start a read-write transaction.
let mut tx = db.begin_tx_mut()?;
// Then perform read-write access.
tx.put(b"hello", b"world")?;
_ = tx.get(b"hello")?;
// Finally, commit the transaction.
tx.commit()?;
// Or you can perform a read-write transaction using closure,
// with `update` method:
db.update(|tx| {
tx.put(b"hello", b"world")?;
_ = tx.get(b"hello")?;
Ok(())
})?;
Swift 示例
// Start a read-write transaction.
let tx = try db.beginTxMut()
// Then perform read-write access.
try tx.put("world".data(using: .utf8)!, for: "hello")
_ = try tx.get("hello")
// Finally, commit the transaction.
try tx.commit()
// Or you can perform a read-write transaction using closure,
// with `update` method:
try db.update {
try $0.put("world".data(using: .utf8)!, for: "hello")
_ = try $0.get("hello")
}
请注意
❗️ 事务实例是不可发送的,这意味着将它们发送到另一个线程是不安全的。Rust 通过利用 Ownership
系统,以及 Send
和 Sync
特性来自动执行这些要求,而 Swift 则要求我们手动确保这些保证。
❗️ 仅仅读事务和读写事务不得重叠,否则会发生死锁。
😺 因此 ThetaDB 建议如果您想使用事务,请使用带有闭包参数的 API(即 view
、update
)。
游标
我们可以使用 Cursor
自由地遍历 ThetaDB 中的数据。
例如,我们可以迭代 ThetaDB 中的所有键值对,如下所示
Rust 示例
// Forward traversal.
let mut cursor = db.first_cursor()?;
while let Some((key, value)) = cursor.key_value()? {
println!("{:?} => {:?}", key, value);
cursor.next()?;
}
// Backward traversal.
let mut cursor = db.last_cursor()?;
while let Some((key, value)) = cursor.key_value()? {
println!("{:?} => {:?}", key, value);
cursor.prev()?;
}
Swift 示例
// Forward traversal.
let cursor = try db.firstCursor()
while let (key, value) = try cursor.keyValue() {
print("\(key) => \(value)")
try cursor.next()
}
// Backward traversal.
let cursor2 = try db.lastCursor()
while let (key, value) = try cursor2.keyValue() {
print("\(key) => \(value)")
try cursor2.previous()
}
或者我们可以这样对 ThetaDB 执行范围查询
Rust 示例
let mut cursor = db.cursor_from_key(b"C")?;
// Enable `let_chains` feature, should add `#![feature(let_chains)]`
// to the crate attributes.
while let Some((key, value)) = cursor.key_value()? && key != b"G" {
println!("{:?} => {:?}", key, value);
cursor.next()?;
}
Swift 示例
let cursor = try db.cursor(key: "C")
while let (key, value) = try cursor.keyValue(), key != "G" {
print("\(key) => \(value)")
try cursor.next()
}
请注意
❗️ 游标也是一种事务(可以理解为一种只读事务),因此它也遵循上述提到的事务注意事项。
基准测试
🚧 待测量。
许可
ThetaDB 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。