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写入定长二进制,省空间也避编码歧义
事情说清了就结束
