C# 操作LMDB文件 C#如何使用闪电内存映射数据库进行键值存储

来源:这里教程网 时间:2026-02-21 17:42:40 作者:

LMDB 在 C# 中没有原生支持,必须用 P/Invoke 封装或选封装好的库

LMDB 是 C 写的嵌入式 KV 数据库,C# 不能直接 new 一个

LmdbDatabase
——它压根不存在。你得要么自己写
DllImport
lmdb.dll
,要么用现成封装,比如
Lmdb.NET
Unosql.LMDB
。前者更活跃、文档稍多,后者更轻但更新慢。别碰那些只有一两个 commit 的“个人封装”,
Environment.Exit(0)
都可能触发内存泄漏。

常见错误现象:

DllNotFoundException: lmdb.dll
(没把 DLL 放对位置)、
AccessViolationException
(指针传错、环境未初始化或已关闭后还访问)。

Windows 下确保
lmdb.dll
和你的
.exe
同目录,或在
PATH
中;Linux/macOS 对应
liblmdb.so
/
liblmdb.dylib
Lmdb.NET
时,必须先调
NativeMethods.mdb_version(out _, out _, out _)
检查是否加载成功,别等
Env.Create()
才崩
所有
Env
Dbi
Txn
对象都必须显式
Dispose()
,它们背后是 unmanaged 内存和文件锁,GC 不管

Env.Open() 失败多半是路径、权限或环境复用问题

Env.Open()
是第一个拦路虎。它不报“路径不存在”,而是直接
MDB_INVALID
MDB_PANIC
——尤其在 Windows 上,路径含中文、空格、符号,或父目录没写入权限时静默失败。

使用场景:首次创建数据库、多进程共享读、单进程多线程读写混用。LMDB 要求数据目录由单一进程创建并持有

mmap
区域,其他进程只能只读打开(除非用
 MDB_NOSUBDIR 
)。

路径必须是绝对路径,
@"C:\data\mydb"
安全,
"./db"
很可能因工作目录不确定而失败
首次调用
Env.Open()
前,确保目录存在且当前用户有读写权限(
Directory.CreateDirectory(path)
不够,还得
DirectoryInfo.CreateSubdirectory()
+ 显式设 ACL)
不要在多个
Env
实例间复用同一个路径,LMDB 不允许多个写环境并发打开同一 DB 目录
若需多进程读,主进程用
EnvFlags.MDB_NOMETASYNC | MDB_NOSYNC
提升性能,但从进程必须加
MDB_RDONLY

Transaction.Commit() 失败通常因为 key/value 超长、txn 超时或磁盘满

LMDB 对单 key/value 有硬限制:

key
最大 511 字节(默认页大小 4KB),
value
理论上可达 2GB,但实际受
Env.SetMapSize()
控制。超了不报“太长”,而是
MDB_BAD_VALSIZE
或直接
Commit()
返回 false。

性能影响明显:频繁小

Commit()
会拖慢吞吐;大事务(如一次写 10 万条)可能触发
MDB_MAP_FULL
——不是数据满了,是内存映射空间不够,得提前
Env.SetMapSize()
扩容。

写前检查
key.Length ,<code>value.Length
别超过当前
MapSize
的 1/10(留余量)
批量写用
WriteBatch
Lmdb.NET
不自带,得手写循环 + 单
Txn
);每 1000–5000 条
Commit()
一次,别攒到内存爆
Env.SetMapSize()
必须在
Env.Open()
之后、任何
Txn
创建之前调,且不能缩小,只能增大
Linux 下注意
/dev/shm
大小(tmpfs),LMDB 默认用它做 lockfile,空间不足会导致
MDB_UNABLE_EXTEND_MAPSIZE

字符串 key/value 编码必须统一,否则查不到也无提示

LMDB 存的是字节流,不认字符串编码。你用

Encoding.UTF8.GetBytes("hello")
存,就得用同样方式取;若存时用
UTF8
,取时用
ASCII
解,结果就是乱码——但不会抛异常,
GetString()
只是按错码表硬转,键匹配直接失败。

容易被忽略的地方:空字符串、null 值、BOM 头。LMDB 允许 key 为

null
(即
IntPtr.Zero
),但 .NET 封装库一般不暴露这个接口,强行传
null
很可能 NRE。

约定全部用
Encoding.UTF8
,并在工具方法里封装:如
ToKey(string s) => Encoding.UTF8.GetBytes(s)
,避免散落各处
key 避免以
\0
开头或含
\0
,C 层会截断;value 含
\0
没问题,LMDB 不当字符串处理
调试时用
unsafe
+
fixed
把 value 字节数组打出来看十六进制,比猜编码靠谱得多
如果要用复合 key(如 user_id+timestamp),别拼字符串,用
BitConverter
写入定长二进制,省空间也避编码歧义

事情说清了就结束

相关推荐