Oracle Berkeley DB 是高效能、支援交易的嵌入式鍵值資料庫(B+ 樹/哈希/隊列/遞迴樹)。.NET 透過 libdb_dotNET45.dll(BDB 4.5/4.7)互操作。核心優勢:零配置、ACID 交易、單機 256TB、千級並發。
using BerkeleyDB; // 引用 libdb_dotNET45.dll
public class BdbManager : IDisposable {
private readonly string _dir, _dbName;
private Env _env;
private Db _db;
private DbBTree _btree;
private Txn _txn;
private readonly BinaryFormatter _formatter = new BinaryFormatter();
private readonly MemoryStream _keyStream = new MemoryStream();
private readonly MemoryStream _dataStream = new MemoryStream();
private bool _committed;
public BdbManager(string directory, string dbName) {
_dir = directory; _dbName = dbName;
Initialize();
}
private void Initialize() {
_env = new Env(EnvCreateFlags.None);
var flags = Env.OpenFlags.Create | Env.OpenFlags.InitLock | Env.OpenFlags.InitLog
| Env.OpenFlags.InitMPool | Env.OpenFlags.InitTxn | Env.OpenFlags.Recover;
_env.Open(_dir, flags, 0);
_txn = _env.TxnBegin(null, Txn.BeginFlags.None);
_db = _env.CreateDatabase(DbCreateFlags.None);
_btree = (DbBTree)_db.Open(_txn, _dbName, null, DbType.BTree, Db.OpenFlags.Create, 0);
}
}
private void ResetStreams() {
_keyStream.Position = 0; _keyStream.SetLength(0);
_dataStream.Position = 0; _dataStream.SetLength(0);
}
private DbEntry SerializeKey(TKey key) {
_keyStream.Position = 0;
_formatter.Serialize(_keyStream, key);
return DbEntry.InOut(_keyStream.GetBuffer(), 0, (int)_keyStream.Position);
}
private DbEntry SerializeValue(TVal val) {
_dataStream.Position = 0;
_formatter.Serialize(_dataStream, val);
return DbEntry.InOut(_dataStream.GetBuffer(), 0, (int)_dataStream.Position);
}
private TVal DeserializeValue(DbEntry entry) {
_dataStream.Position = 0;
_dataStream.SetLength(entry.Size);
return (TVal)_formatter.Deserialize(_dataStream);
}
public bool Put(TKey key, TVal value) where TVal : class {
ResetStreams();
var k = SerializeKey(key);
var v = SerializeValue(value);
var status = _btree.Put(_txn, ref k, ref v);
return status == WriteStatus.Success;
}
public bool Get(TKey key, out TVal value) where TVal : class {
var k = SerializeKey(key);
var v = DbEntry.Out(_dataStream.GetBuffer()); // 初始緩衝區
while (true) {
var status = _btree.Get(_txn, ref k, ref v, DbFile.ReadFlags.None);
switch (status) {
case ReadStatus.Success:
value = DeserializeValue(v);
return true;
case ReadStatus.BufferSmall:
// 動態擴容
if (k.Buffer.Length < k.Size) {
_keyStream.SetLength(k.Size);
k = DbEntry.Out(_keyStream.GetBuffer());
}
if (v.Buffer.Length < v.Size) {
_dataStream.SetLength(v.Size);
v = DbEntry.Out(_dataStream.GetBuffer());
}
continue;
default:
value = default;
return false;
}
}
}
public bool Delete(TKey key) {
var k = SerializeKey(key);
var status = _btree.Delete(_txn, ref k);
return status == DeleteStatus.Success || status == DeleteStatus.NotFound;
}
public void Commit() {
_txn.Commit(Txn.CommitMode.None);
_committed = true;
}
public void Dispose() {
if (!_committed) Commit();
_db?.Close();
_env?.Close();
}
public IEnumerable Scan() where TVal : class {
using (var cursor = _btree.OpenCursor(_txn, DbFileCursor.CreateFlags.None)) {
var k = DbEntry.Out(_keyStream.GetBuffer());
var v = DbEntry.Out(_dataStream.GetBuffer());
while (true) {
var status = cursor.Get(ref k, ref v, DbFileCursor.GetMode.Next, DbFileCursor.ReadFlags.None);
if (status == ReadStatus.NotFound) yield break;
if (status == ReadStatus.KeyEmpty) continue; // 跳過已刪除
if (status == ReadStatus.BufferSmall) { /* 擴容邏輯同 Get */ continue; }
if (status == ReadStatus.Success) {
_dataStream.Position = 0;
_dataStream.SetLength(v.Size);
yield return (TVal)_formatter.Deserialize(_dataStream);
}
}
}
}
[Serializable]
public class Item : IKeyProvider {
public string Name { get; set; } // 作為 Key
public string Text { get; set; }
public int Id { get; set; }
public string Key => Name;
}
using (var mgr = new BdbManager("db", "items.dat")) {
mgr.Put("key1", new Item { Id = 1, Name = "key1", Text = "Hello" });
mgr.Commit();
}
using (var mgr = new BdbManager("db", "items.dat")) {
if (mgr.Get("key1", out Item item)) Console.WriteLine(item.Text);
foreach (var it in mgr.Scan- ()) Console.WriteLine($"{it.Key}: {it.Text}");
}
BinaryFormatter 版本敏感,生產環境建議改用 protobuf-net、MessagePack 或 System.Text.Json(需自訂 DbEntry 序列化)。Env.Open 目錄需寫入權限,包含日誌、鎖、緩存檔案。InitLock + InitTxn 支援多執行緒/進程並發,鎖衝突會拋出 DeadlockException 需重試。