{"id":31921891,"url":"https://github.com/rmw-lib/mdbx","last_synced_at":"2025-10-13T22:55:24.424Z","repository":{"id":57637561,"uuid":"431341669","full_name":"rmw-lib/mdbx","owner":"rmw-lib","description":null,"archived":false,"fork":false,"pushed_at":"2021-12-18T01:12:17.000Z","size":346,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-14T07:27:15.630Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rmw-lib.png","metadata":{"files":{"readme":"README.cn.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-11-24T04:04:48.000Z","updated_at":"2024-09-09T01:56:04.000Z","dependencies_parsed_at":"2022-09-02T03:21:52.663Z","dependency_job_id":null,"html_url":"https://github.com/rmw-lib/mdbx","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/rmw-lib/mdbx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmw-lib%2Fmdbx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmw-lib%2Fmdbx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmw-lib%2Fmdbx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmw-lib%2Fmdbx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rmw-lib","download_url":"https://codeload.github.com/rmw-lib/mdbx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmw-lib%2Fmdbx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279017087,"owners_count":26085984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-13T22:55:14.725Z","updated_at":"2025-10-13T22:55:24.418Z","avatar_url":"https://github.com/rmw-lib.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- 本文件由 ./readme.make.md 自动生成，请不要直接修改此文件 --\u003e\n\n# libmdbx 的 rust 封装\n\n[libmdbx](https://github.com/erthink/libmdbx) 数据库的 `rust` 封装。\n\n---\n\n目录 :\n\n[[toc]]\n\n---\n\n## 引子\n\n在写『[人民网络](https://rmw.link)』的时候，感觉自己需要一个嵌入式数据库。\n\n因为涉及到网络吞吐的记录，读写频繁，`sqlite3` 太高级性能堪忧。\n\n所以用更底层的键值数据库更为合适（[lmdb 比 sqlite 快 10 倍](https://discourse.world/h/2020/06/05/Shine-and-poverty-key-value-database-LMDB-in-applications-for-iOS)）。\n\n![](https://raw.githubusercontent.com/gcxfd/img/gh-pages/yxZV8x.jpg)\n\n最终，我选择了 `lmdb` 的魔改版 —— `mdbx` 。\n\n目前，现有的 `mdbx` 的 `rust` 封装 [mdbx-rs(mdbx-sys)不支持 windows](https://github.com/vorot93/mdbx-rs/issues/1)，于是我自己动手封装一个支持 windows 的版本。\n\n支持存储自定义 rust 类型。 支持多线程访问。\n\n可以一个模块中用 `lazy_static` 定义好数据库，然后用简单引入并使用，比如:\n\n```rust\nuse db::User;\n\nlet id = 1234;\nlet user = r!(User.get id);\n```\n\n## libmdbx 是什么？\n\n[mdbx](https://github.com/erthink/libmdbx) 是基于 lmdb 二次开发的数据库 ，作者是俄罗斯人 [Леонид Юрьев (Leonid Yuriev)](https://vk.com/erthink)。\n\n[lmdb](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) 是一个超级快的嵌入式键值数据库。\n\n全文搜索引擎 [MeiliSearch](https://docs.meilisearch.com/reference/under_the_hood/storage.html#measured-disk-usage) 就是基于 lmdb 开发的。\n\n[深度学习框架 caffe 也用 lmdb 作为数据存储](https://docs.nvidia.com/deeplearning/dali/user-guide/docs/examples/general/data_loading/dataloading_lmdb.html)。\n\n[mdbx 在嵌入式性能测试基准 ioarena 中 lmdb 还要快 30%](https://github.com/erthink/libmdbx#added-features) 。\n\n![](https://raw.githubusercontent.com/wiki/erthink/libmdbx/img/perf-slide-1.png)\n![](https://raw.githubusercontent.com/wiki/erthink/libmdbx/img/perf-slide-3.png)\n![](https://raw.githubusercontent.com/wiki/erthink/libmdbx/img/perf-slide-4.png)\n![](https://raw.githubusercontent.com/wiki/erthink/libmdbx/img/perf-slide-5.png)\n\n与此同时，[mdbx 改进了不少 lmdb 的缺憾](https://github.com/erthink/libmdbx#improvements-beyond-lmdb)，因此 Erigon（下一代以太坊客户端）最近从 LMDB 切换到了 MDBX [^erigon] 。\n\n## 使用教程\n\n### 如何运行示例\n\n首先克隆代码库 `git clone git@github.com:rmw-lib/mdbx.git --depth=1 \u0026\u0026 cd mdbx`\n\n然后运行 `cargo run --example 01` ，就运行了 `examples/01.rs`\n\n如果是自己的项目，请先运行 :\n\n```bash\ncargo install cargo-edit\ncargo add mdbx lazy_static ctor paste\n```\n\n\n### 示例 1 : 写 `set(key,val)` 和 读 `.get(key)`\n\n我们先来看一个简单的例子 [examples/01.rs](https://github.com/rmw-lib/mdbx/blob/master/examples/01.rs)\n\n#### 代码\n\n```rust\nuse anyhow::{Ok, Result};\nuse mdbx::prelude::*;\n\nenv_rw!(\n  MDBX,\n  {\n    let mut db_path = std::env::current_exe().unwrap();\n    db_path.set_extension(\"mdb\");\n    println!(\"mdbx file path {}\", db_path.display());\n    db_path.into()\n  },\n  r,\n  w\n);\n\nmdbx! {\n  MDBX // 数据库 Env 的变量名\n  Test // 数据库 Test\n}\n\nfn main() -\u003e Result\u003c()\u003e {\n  // 输出libmdbx的版本号\n  unsafe {\n    println!(\n      \"mdbx version https://github.com/erthink/libmdbx/releases/tag/v{}.{}.{}\",\n      mdbx_version.major, mdbx_version.minor, mdbx_version.release\n    );\n  }\n\n  // 多线程读写\n  let t = std::thread::spawn(|| {\n    let tx = w!();\n    let test = tx | Test;\n    test.set([1, 2], [6])?;\n    println!(\"test1 get {:?}\", test.get([1, 2]));\n\n    match test.get([1, 2])? {\n      Some(val) =\u003e {\n        let t: \u0026[u8] = \u0026val;\n        println!(\"{:?}\", t);\n      }\n      None =\u003e unreachable!(),\n    }\n    Ok(())\n  });\n\n  t.join().unwrap()?;\n\n  Ok(())\n}\n```\n\n#### 运行输出\n\n```\nmdbx file path /Users/z/rmw/mdbx/target/debug/examples/01.mdb\nmdbx version https://github.com/erthink/libmdbx/releases/tag/v0.11.2\ntest1 get Ok(Some(Bin([6])))\n[6]\n```\n\n#### 代码说明\n\n##### `env_rw!` 定义数据库\n\n代码一开始使用了一个宏 env_rw，这个宏有 4 个参数。\n\n1. 数据库环境的变量名\n\n2. 返回一个  对象，[mdbx:: env:: Config](https://docs.rs/mdbx/latest/src/mdbx/env.rs.html#27-35) 。\n\n   我们使用默认配置，因为 `Env` 实现了 `From\u003cInto\u003cPathBuf\u003e\u003e`，所以数据库路径 `into()` 即可，默认配置如下。\n\n   ```rust\n   #[derive(Clone, Debug)]\n   pub struct Config {\n     path: PathBuf,\n     mode: ffi::mdbx_mode_t,\n     flag: flag::ENV,\n     sync_period: u64,\n     sync_bytes: u64,\n     max_db: u64,\n     pagesize: isize,\n   }\n\n   lazy_static! {\n     pub static ref ENV_CONFIG_DEFAULT: Config = Config {\n       path:PathBuf::new(),\n       mode: 0o600,\n       //https://github.com/erthink/libmdbx/issues/248\n       sync_period : 65536, // 以 1/65536 秒为单位\n       sync_bytes : 65536,\n       max_db : 256,\n       flag : (\n           flag::ENV::MDBX_EXCLUSIVE\n         | flag::ENV::MDBX_LIFORECLAIM\n         | flag::ENV::MDBX_COALESCE\n         | flag::ENV::MDBX_NOMEMINIT\n         | flag::ENV::MDBX_NOSUBDIR\n         | flag::ENV::MDBX_SAFE_NOSYNC\n         // | flag::ENV::MDBX_SYNC_DURABLE\n       ),\n       pagesize:-1\n     };\n   }\n   ```\n\n   `max_db` 是最大的数据库个数，[最多 32765 个数据库](https://github.com/erthink/libmdbx)，这个设置可以在每次打开数据库时重设，设置太大会影响性能，按需设置即可。\n\n   其他参数含义参见 [libmdbx 的文档](https://erthink.github.io/libmdbx/group__c__opening.html#ga9138119a904355d245777c4119534061) 。\n\n\n3. 数据库读事务宏的名称，默认值为 `r`\n\n4. 数据库写事务宏的名称，默认值为 `w`\n\n其中 3、4 参数可以省略使用默认值。\n\n##### 宏展开\n\n如果想看看宏魔法到底干了什么，可以用 `cargo expand --example 01` 宏展开，此指令需要先安装 `cargo install cargo-expand`\n\n展开后的代码截图如下：\n\n![PDzEtT](https://raw.githubusercontent.com/gcxfd/img/gh-pages/PDzEtT.png)\n\n##### anyhow 和 lazy_static\n\n从展开后的截图，可以看到，使用了 `lazy_static` 和 `anyhow`。\n\n[anyhow](https://rustmagazine.github.io/rust_magazine_2021/chapter_2/rust_error_handle.html#thiserror--anyhow) 是 rust 的错误处理库。\n\n[lazy_static](https://juejin.cn/post/7007336922817232927) 是延迟初始化的静态变量。\n\n这两个库很常见，我不赘言。\n\n##### 宏 mdbx!\n\n[`mdbx!`](https://docs.rs/mdbx-proc/latest/src/mdbx_proc/lib.rs.html) 是一个 [过程宏](https://mp.weixin.qq.com/s/YT_HNFDCQ_IyocvBkRNJnA)。\n\n\n```rust\nmdbx! {\n MDBX // 数据库 Env 的变量名\n Test // 数据库 Test\n}\n```\n\n第一行参数是数据库环境的变量名\n\n第二行是数据库的名称\n\n数据库可有多个，每个一行\n\n##### 线程与事务\n\n上面代码中演示了多线程读写。\n\n值得注意的是，**同一线程同一时间只能有一个事务，如果某线程打开了多个事务会程序会崩溃**。\n\n事务会在作用域结束时提交。\n\n##### 读写二进制数据\n\n```rust\nlet tx = w!();\nlet test = tx | Test;\ntest.set([1, 2], [6])?;\nprintln!(\"test1 get {:?}\", test.get([1, 2]));\n\nmatch test.get([1, 2])? {\n Some(val) =\u003e {\n  let t:\u0026[u8] = \u0026val;\n  println!(\"{:?}\",t);\n },\n None =\u003e unreachable!()\n}\n```\n\n`set` 是写，`get` 是读，任何实现了 [`AsRef\u003c[u8]\u003e`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) 的对象都可以写入数据库。\n\n`get` 出来的东西是 `Ok(Some(Bin([6])))`，可以转为 `\u0026[u8]`。\n\n### 示例 2 : 数据类型、数据库标志 、删除、遍历\n\n我们来看第二个例子 [examples/02.rs](https://github.com/rmw-lib/mdbx/blob/master/examples/02.rs) :\n\n这个例子中，`env_rw!` 省略了，第三、第四个参数（`r`, `w`）。\n\n#### 代码\n\n```rust\nuse anyhow::{Ok, Result};\nuse mdbx::prelude::*;\n\nenv_rw!(MDBX, {\n  let mut db_path = std::env::current_exe().unwrap();\n  db_path.set_extension(\"mdb\");\n  println!(\"mdbx file path {}\", db_path.display());\n  db_path.into()\n});\n\nmdbx! {\n  MDBX // 数据库ENV的变量名\n  Test1\n  Test2\n    key Str\n    val Str\n  Test3\n    key i32\n    val u64\n  Test4\n    key u64\n    val u16\n    flag DUPSORT\n}\n\nfn main() -\u003e Result\u003c()\u003e {\n  // 快捷写入\n  w!(Test1.set [2, 3],[4, 5]);\n\n  // 快捷读取\n  match r!(Test1.get [2, 3]) {\n    Some(r) =\u003e {\n      println!(\n        \"\\nu16::from_le_bytes({:?}) = {}\",\n        r,\n        u16::from_le_bytes((*r).try_into()?)\n      );\n    }\n    None =\u003e unreachable!(),\n  }\n\n  // 在同一个事务中对多个数据库进行多个操作\n  {\n    let tx = w!();\n    let test1 = tx | Test1;\n\n    test1.set(\u0026[9], \u0026[10, 12])?;\n    test1.set([8, 1], [9])?;\n    test1.set(\"rmw.link\", \"Down with Data Hegemony\")?;\n    test1.set(\u0026\"abc\", \u0026\"012\")?;\n\n    println!(\"\\n-- loop test1\");\n    for (k, v) in test1 {\n      println!(\"{} = {}\", k, v);\n    }\n\n    dbg!(test1.del_val([8, 1], [3])?);\n    dbg!(test1.get([8, 1])?.unwrap());\n    dbg!(test1.del_val([8, 1], [9])?);\n    dbg!(test1.get([8, 1])?);\n\n    dbg!(test1.del([9])?);\n    dbg!(test1.get([9])?);\n    dbg!(test1.del([9])?);\n\n    let test2 = tx | Test2;\n    test2.set(\"rmw.link\", \"Down with Data Hegemony\")?;\n    test2.set(\u0026\"abc\", \u0026\"012\")?;\n    println!(\"\\n-- loop test2\");\n    for (k, v) in test2 {\n      println!(\"{} = {}\", k, v);\n    }\n\n    let test3 = tx | Test3;\n\n    test3.set(13, 32)?;\n    test3.set(16, 32)?;\n    test3.set(-15, 6)?;\n    test3.set(-10, 6)?;\n    test3.set(-12, 6)?;\n    test3.set(0, 6)?;\n    test3.set(10, 5)?;\n\n    println!(\"\\n-- loop test3\");\n    for (k, v) in test3 {\n      println!(\"{:?} = {:?}\", k, v);\n    }\n\n    let test4 = tx | Test4;\n    test4.set(10, 5)?;\n    test4.set(10, 0)?;\n    test4.set(13, 32)?;\n    test4.set(16, 2)?;\n    test4.set(16, 1)?;\n    test4.set(16, 3)?;\n    test4.set(0, 6)?;\n    test4.set(10, 5)?;\n    test4.set(0, 2)?;\n\n    dbg!(test4.del_val(0, 2)?);\n    dbg!(test4.del_val(0, 2)?);\n\n    println!(\"\\n-- loop test4 rev\");\n    for (k, v) in test4.rev() {\n      println!(\"{:?} = {:?}\", k, v);\n    }\n\n    for i in test4.dup(16) {\n      println!(\"dup(16) {:?}\", i);\n    }\n\n    // 事务会在作用域的结尾提交\n  }\n\n  Ok(())\n}\n```\n\n#### 运行输出\n\n\n```\nmdbx file path /Users/z/rmw/mdbx/target/debug/examples/02.mdb\n\nu16::from_le_bytes(Bin([4, 5])) = 1284\n\n-- loop test1\n[2] = [3]\n[2, 3] = [4, 5]\n[8, 1] = [9]\n[9] = [10, 12]\n[97, 98, 99] = [48, 49, 50]\n[114, 109, 119, 46, 108, 105, 110, 107] = [68, 111, 119, 110, 32, 119, 105, 116, 104, 32, 68, 97, 116, 97, 32, 72, 101, 103, 101, 109, 111, 110, 121]\n[examples/02.rs:57] test1.del_val([8, 1], [3])? = false\n[examples/02.rs:58] test1.get([8, 1])?.unwrap() = Bin(\n    [\n        9,\n    ],\n)\n[examples/02.rs:59] test1.del_val([8, 1], [9])? = true\n[examples/02.rs:60] test1.get([8, 1])? = None\n[examples/02.rs:62] test1.del([9])? = true\n[examples/02.rs:63] test1.get([9])? = None\n[examples/02.rs:64] test1.del([9])? = false\n\n-- loop test2\nabc = 012\nrmw.link = Down with Data Hegemony\n\n-- loop test3\n0 = 6\n10 = 5\n13 = 32\n16 = 32\n-15 = 6\n-12 = 6\n-10 = 6\n[examples/02.rs:100] test4.del_val(0, 2)? = true\n[examples/02.rs:101] test4.del_val(0, 2)? = false\n\n-- loop test4 rev\n16 = 3\n16 = 2\n16 = 1\n13 = 32\n10 = 5\n10 = 0\n0 = 6\ndup(16) 1\ndup(16) 2\ndup(16) 3\n```\n\n#### 快捷读写\n\n若只是想简单的读取或写入单行数据，我们可以用宏的语法糖。\n\n读数据\n\n```\nr!(Test1.get [2, 3])\n```\n\n写数据\n\n```rust\nw!(Test1.set [2, 3],[4, 5])\n```\n\n\n都一行搞定， 正如 [examples/02.rs](https://github.com/rmw-lib/mdbx/blob/master/examples/02.rs) 写的那样。\n\n#### 数据类型\n\n在 [examples/02.rs](https://github.com/rmw-lib/mdbx/blob/master/examples/02.rs) 中，数据库定义是这样的 :\n\n```rust\nTest2 // 数据库 Test2\n  key Str\n  val Str\nTest3 // 数据库 Test2\n  key i32\n  val u64\nTest4 // 数据库 Test3\n  key u64\n  val u16\n  flag DUPSORT\n```\n\n其中 `key` 和 `val` 分别定义了键和值的数据类型。\n\n如果试图写入的数据类型和定义的不匹配，会报错，截图如下 :\n\n![](https://raw.githubusercontent.com/gcxfd/img/gh-pages/4rFTC6.png)\n\n默认的数据类型是 [`Bin`](https://docs.rs/mdbx/latest/mdbx/type/struct.Bin.html) ，任何实现了 `AsRef\u003c[u8]\u003e` 的数据都可以写入。\n\n如果键或值是 `utf8` 字符串，可设置数据类型为 [`Str`](https://docs.rs/mdbx/latest/mdbx/type/struct.Str.html) 。\n\n对 `Str` [解引用](https://doc.rust-lang.org/std/ops/trait.Deref.html) 会返回字符串，类似 `let k:\u0026str = \u0026k;`。\n\n此外，`Str` 还实现了 [`std::fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)，`println!(\"{}\",k)` 时将输出可读的字符串。\n\n##### 预置数据类型\n\n除了 `Str` 和 `Bin` ，封装还自带了对 [usize, u128, u64, u32, u16, u8, isize, i128, i64, i32, i16, i8, f32, f64](https://docs.rs/mdbx/latest/src/mdbx/type.rs.html#48) 的数据支持。\n\n#### 数据库标志\n\n可以看到 [examples/02.rs](https://github.com/rmw-lib/mdbx/blob/master/examples/02.rs) 中 `Test4` 数据加上了数据库标志 `flag DUPSORT`\n\nlibmdbx 数据库有很多标志( [`MDBX_db_flags_t`](https://erthink.github.io/libmdbx/group__c__dbi.html#gafe3bddb297b3ab0d828a487c5726f76a) ) 可以设置。\n\n* REVERSEKEY 对键使用反向字符串比较。（当使用小端编码数字作为键的时候很有用）\n* DUPSORT 使用排序的重复项，即允许一个键有多个值。\n* INTEGERKEY 本机字节顺序的数字键 uint32_t 或 uint64_t。键的大小必须相同，并且在作为参数传递时必须对齐。\n* DUPFIXED 使用 DUPSORT 的情况下，数据值的大小必须相同（可以快速统计值的个数）。\n* INTEGERDUP 需使用 DUPSORT 和 DUPFIXED；值是整数（类似 INTEGERKEY）。数据值必须全部具有相同的大小，并且在作为参数传递时必须对齐。\n* REVERSEDUP 使用 DUPSORT；对数据值使用反向字符串比较。\n* CREATE 如果不存在，则创建 DB （默认已加上）。\n* DB_ACCEDE 打开使用未知标志创建的现有子数据库。\n  该 DB_ACCEDE 标志旨在打开使用未知标志（REVERSEKEY、DUPSORT、INTEGERKEY、DUPFIXED、INTEGERDUP 和 REVERSEDUP）创建的现有子数据库。\n  在这种情况下，子数据库不会返回 INCOMPATIBLE 错误，而是使用创建它的标志打开，然后应用程序可以通过 mdbx_dbi_flags()确定实际标志。\n\n##### DUPSORT : 一个键对应多个值\n\n`DUPSORT`，意味着一个键可以对应多个值。\n\n如果要设置多个标志，写法如 `flag DUPSORT | DUPFIXED`\n\n##### `.dup(key)` 返回某键所有对应的值的迭代器\n\n只有标记了 `DUPSORT` 一个键可以对应多个值的数据库，才有这个函数。\n\n对于 `DUPSORT` 数据库，`get` 只返回此键的第一个值。想获取所有值，请用 `dup`。\n\n##### 默认自动追加的数据库标志\n\n当数据类型为 `u32` / `u64` / `usize` 的时候， 会自动加上数据库标志 [`INTEGERKEY`](https://docs.rs/mdbx-proc/latest/src/mdbx_proc/lib.rs.html#105)。\n\n在小端编码的机器上，其他数字类型会自动加上 [`REVERSEKEY`](https://docs.rs/mdbx-proc/latest/src/mdbx_proc/lib.rs.html#108)。\n\n#### 删除数据\n\n##### `.del(key)` 删除键\n\n`.del(val)` 会删除某个键对应的值。\n\n如果数据库有标志 `DUPSORT`，将会删除这个键下的所有值。\n\n如果有数据被删除的时候返回 `true`，反之返回 `false`。\n\n##### `.del_val(key,val)` 精确匹配的删除\n\n`.del_val(key,val)` 会删除和输入参数完全一致键值对。\n\n如果有数据被删除的时候返回 `true`，反之返回 `false`。\n\n#### 遍历\n\n##### 顺序遍历\n\n因为实现了 [`std::iter::IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) ，可以直接如下遍历 :\n\n`for (k, v) in test1`\n\n##### `.rev()` 倒序遍历\n\n`for (k, v) in test4.rev()`\n\n##### 排序方式\n\nlibmdbx 的键值都是按 [字典序](https://zh.wikipedia.org/wiki/%E5%AD%97%E5%85%B8%E5%BA%8F) 排列的。\n\n* 对于无符号数字\n\n  因为自动加上了数据库标志（ `u32`/`u64`/`usize` 会加上 `INTEGERKEY`，其他根据机器编码自动判断是否加上 `REVERSEKEY` ） ，会按数字从小到大的顺序排列。\n\n* 对于有符号数字\n\n  顺序是：0 在第一个，然后从小到大遍历所有正数，然后从小到大遍历所有负数。\n\n### 区间迭代器\n\n```rust\nuse anyhow::Result;\nuse mdbx::prelude::*;\n\nenv_rw!(MDBX, {\n  let mut db_path = std::env::current_exe().unwrap();\n  db_path.set_extension(\"mdb\");\n  println!(\"mdbx file path {}\", db_path.display());\n  db_path.into()\n});\n\nmdbx! {\n  MDBX\n  Test0\n  Test1\n    key u16\n    val u64\n    flag DUPSORT\n  Test2\n    key u32\n    val u64\n}\n\nmacro_rules! range_rev {\n  ($var:ident, $range:expr) =\u003e {\n    println!(\"\\n# {}.rev_range({:?})\", stringify!($var), $range);\n    for i in $var.range_rev($range) {\n      println!(\"{:?}\", i);\n    }\n  };\n}\n\nmacro_rules! range {\n  ($var:ident, $range:expr) =\u003e {\n    println!(\"\\n# {}.range({:?})\", stringify!($var), $range);\n    for i in $var.range($range) {\n      println!(\"{:?}\", i);\n    }\n  };\n}\n\nfn main() -\u003e Result\u003c()\u003e {\n  {\n    println!(\"\\n\u003e Test0\");\n    let tx = \u0026MDBX.w()?;\n    let test0 = tx | Test0;\n    test0.set([0], [0, 1])?;\n    test0.set([1], [1, 2])?;\n    test0.set([2], [2, 3])?;\n    test0.set([1, 1], [1, 3])?;\n    test0.set([1, 2], [1, 3])?;\n    test0.set([3], [])?;\n\n    range!(test0, [1]..);\n    let begin: \u0026[u8] = \u0026[1, 1];\n    range!(test0, begin..=\u0026[2]);\n  }\n\n  {\n    let tx = \u0026MDBX.w()?;\n\n    let test1 = tx | Test1;\n    test1.set(2, 9)?;\n    test1.set(2, 4)?;\n    test1.set(9, 7)?;\n    test1.set(3, 0)?;\n    test1.set(3, 8)?;\n    test1.set(5, 3)?;\n    test1.set(5, 8)?;\n    test1.set(9, 1)?;\n    println!(\"-- all\");\n    for i in test1 {\n      println!(\"{:?}\", i);\n    }\n    range!(test1, 1..3);\n    range!(test1, 5..2);\n    range!(test1, 1..=3);\n    range!(test1, ..3);\n    range!(test1, 3..);\n    range_rev!(test1, ..1);\n    range_rev!(test1, ..=1);\n  }\n\n  {\n    println!(\"\\n\u003e Test2\");\n    let tx = \u0026MDBX.w()?;\n    let test2 = tx | Test2;\n    test2.set(2, 9)?;\n    test2.set(1, 2)?;\n    test2.set(2, 4)?;\n    test2.set(1, 5)?;\n    test2.set(9, 7)?;\n    test2.set(9, 1)?;\n    test2.set(0, 0)?;\n\n    range!(test2, 1..3);\n    range!(test2, 1..=3);\n    range!(test2, ..3);\n    range!(test2, 2..);\n    range_rev!(test2, ..1);\n    range_rev!(test2, 2..);\n    range_rev!(test2, ..=1);\n  }\n\n  Ok(())\n}\n```\n\n#### 运行输出\n\n```\nmdbx file path /Users/z/rmw/mdbx/target/debug/examples/range.mdb\n\n\u003e Test0\n\n# test0.range([1]..)\n(Bin([1]), Bin([1, 2]))\n(Bin([1, 1]), Bin([1, 3]))\n(Bin([1, 2]), Bin([1, 3]))\n(Bin([2]), Bin([2, 3]))\n(Bin([3]), Bin([]))\n\n# test0.range([1, 1]..=[2])\n(Bin([1, 1]), Bin([1, 3]))\n(Bin([1, 2]), Bin([1, 3]))\n(Bin([2]), Bin([2, 3]))\n-- all\n(2, 4)\n(2, 9)\n(3, 0)\n(3, 8)\n(5, 3)\n(5, 8)\n(9, 1)\n(9, 2)\n(9, 7)\n\n# test1.range(1..3)\n(2, 4)\n(2, 9)\n\n# test1.range(5..2)\n(5, 8)\n(5, 3)\n(3, 8)\n(3, 0)\n\n# test1.range(1..=3)\n(2, 4)\n(2, 9)\n(3, 0)\n(3, 8)\n\n# test1.range(..3)\n(2, 4)\n(2, 9)\n\n# test1.range(3..)\n(3, 0)\n(3, 8)\n(5, 3)\n(5, 8)\n(9, 1)\n(9, 2)\n(9, 7)\n\n# test1.rev_range(..1)\n(9, 7)\n(9, 2)\n(9, 1)\n(5, 8)\n(5, 3)\n(3, 8)\n(3, 0)\n(2, 9)\n(2, 4)\n\n# test1.rev_range(..=1)\n(9, 7)\n(9, 2)\n(9, 1)\n(5, 8)\n(5, 3)\n(3, 8)\n(3, 0)\n(2, 9)\n(2, 4)\n\n\u003e Test2\n\n# test2.range(1..3)\n(1, 5)\n(2, 4)\n\n# test2.range(1..=3)\n(1, 5)\n(2, 4)\n\n# test2.range(..3)\n(0, 0)\n(1, 5)\n(2, 4)\n\n# test2.range(2..)\n(2, 4)\n(9, 1)\n\n# test2.rev_range(..1)\n(9, 1)\n(2, 4)\n\n# test2.rev_range(2..)\n(2, 4)\n(1, 5)\n(0, 0)\n\n# test2.rev_range(..=1)\n(9, 1)\n(2, 4)\n(1, 5)\n```\n\n#### `.range(begin..end)` 区间迭代\n\n对于数字来说，区间就是数字区间。\n\n对于二进制来说，一样可以构建区间，如：\n\n```\nlet begin : \u0026[u8] = \u0026[1,1];\nfor (k,v) in test0.range(begin..=\u0026[2]) {}\n```\n\n如果 `begin` 大于 `end`，将会倒序迭代。\n\n比如 `test1.range(5..2)`  输出如下 :\n\n```rust\n(5, 8)\n(5, 3)\n(3, 8)\n(3, 0)\n```\n\n区间迭代不支持 [`RangeFull`](https://doc.rust-lang.org/std/ops/struct.RangeFull.html)，也就是不支持用 `..`，请改用上文提到的 [遍历](#遍历) 。\n\n#### `.rev_range` 倒序区间\n\n如果想获取小于等于某个值的倒序区间，可以这样\n\n```\ntest2.rev_range(2..)\n```\n\n将输出\n\n```\n(2, 4)\n(1, 5)\n(0, 0)\n```\n\n倒序区间的 `begin` 或 `end` 必须有一个不设置，因为这种情况下，你总是可以用 `range(end..begin)` 来实现同样的效果。\n\n### 自定义数据类型\n\n演示代码见 [github.com/rmw-lib/mdbx-example/01](https://github.com/rmw-lib/mdbx-example/blob/master/01/src/main.rs)\n\n```rust\nuse anyhow::Result;\nuse mdbx::prelude::*;\nuse speedy::{Readable, Writable};\n\n#[derive(PartialEq, Debug, Readable, Writable)]\npub struct City {\n  name: String,\n  lnglat: (u32, u32),\n}\n\nimpl FromMdbx for City {\n  fn from_mdbx(_: PtrTx, val: MDBX_val) -\u003e Self {\n    Self::read_from_buffer(val_bytes!(val)).unwrap()\n  }\n}\n\nimpl ToAsRef\u003cCity, Vec\u003cu8\u003e\u003e for City {\n  fn to_as_ref(\u0026self) -\u003e Vec\u003cu8\u003e {\n    self.write_to_vec().unwrap()\n  }\n}\n\nenv_rw!(MDBX, {\n  let mut db_path = std::env::current_exe().unwrap();\n  db_path.set_extension(\"mdb\");\n  db_path.into()\n});\n\nmdbx! {\n  MDBX\n  Test\n    key u16\n    val City\n}\n\nfn main() -\u003e Result\u003c()\u003e {\n  let city = City {\n    name: \"BeiJing\".into(),\n    lnglat: (11640, 3990),\n  };\n\n  let tx = w!();\n  let test = tx | Test;\n  test.set(1, city)?;\n  println!(\"{:?}\", test.get(1)?);\n\n  Ok(())\n}\n```\n\n输出如下\n\n```\nSome(City { name: \"BeiJing\", lnglat: (11640, 3990) })\n```\n\n\n在自定义类型的示例中，我们使用 [`speedy`](https://github.com/koute/speedy) 做序列化（[`speedy` 性能评测](https://github.com/djkoloski/rust_serialization_benchmark)）。\n\n自定义类型实现 [`FromMdbx`](https://docs.rs/mdbx/latest/mdbx/type/trait.FromMdbx.html) 和 [`ToAsRef`](https://docs.rs/mdbx/latest/mdbx/type/trait.ToAsRef.html) 后就可以被存入 `mdbx` 了。\n\n如果你使用某种特定的序列化库，还可以自定义 [属性式宏](https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/) 来简化整个流程。\n\n#### 用属性式宏简化自定义类型\n\n实现一个属性宏很简单，比如 [`mdbx_speedy`](https://crates.io/crates/mdbx_speedy) 属性式宏代码如下 :\n\n```rust\nextern crate proc_macro;\nextern crate syn;\n#[macro_use]\nextern crate quote;\n\nuse proc_macro::TokenStream;\n\n#[proc_macro_derive(MdbxSpeedy)]\npub fn mdbx_speedy(ts: TokenStream) -\u003e TokenStream {\n  let ast: syn::DeriveInput = syn::parse(ts).unwrap();\n  let name = \u0026ast.ident;\n  quote! {\n    impl mdbx::prelude::FromMdbx for #name {\n      fn from_mdbx(_: mdbx::prelude::PtrTx, val: mdbx::prelude::MDBX_val) -\u003e Self {\n        Self::read_from_buffer(val_bytes!(val)).unwrap()\n      }\n    }\n\n    impl mdbx::prelude::ToAsRef\u003c#name, Vec\u003cu8\u003e\u003e for #name {\n      fn to_as_ref(\u0026self) -\u003e Vec\u003cu8\u003e {\n        self.write_to_vec().unwrap()\n      }\n    }\n\n  }\n  .into()\n}\n```\n\n在自己的项目中先 `cargo add mdbx-speedy`， 然后就可以快速自定义类型了 ( 演示代码见 [github.com/rmw-lib/mdbx-example/02](https://github.com/rmw-lib/mdbx-example/blob/master/02/src/main.rs) )。\n\n```rust\nuse anyhow::Result;\nuse mdbx::prelude::*;\nuse mdbx_speedy::MdbxSpeedy;\nuse speedy::{Readable, Writable};\n\n#[derive(PartialEq, Debug, Readable, Writable, MdbxSpeedy)]\npub struct City {\n  name: String,\n  lnglat: (u32, u32),\n}\n```\n\n当然重复写 `#[derive(PartialEq, Debug, Readable, Writable, MdbxSpeedy)]` 还是很烦人，可以用 [`derive_alias`](https://docs.rs/derive-alias/0.1.0/derive_alias) 进一步简化代码。\n\n## 使用注意\n\n### 键的长度\n\n- 最小 0，最大≈½页大小（默认 4K 页键最大大小为 2022 字节），初始化数据库时设置 `pagesize` 可以配置，不超过 `65536`，需要是 2 的幂倍数。\n\n## 脚注\n\n[^erigon]: [Erigon（下一代以太坊客户端）最近从 LMDB 切换到了 MDBX。](https://github.com/ledgerwatch/erigon/wiki/Criteria-for-transitioning-from-Alpha-to-Beta#switch-from-lmdb-to-mdbx)\n\n他们列举了从 LMDB 过渡到 MDBX 的好处：\n\n\u003e Erigon 开始使用 BoltDB 数据库后端，然后增加了对 BadgerDB 的支持，最后完全迁移到 LMDB。在某些时候，我们遇到了稳定性问题，这些问题是由我们对 LMDB 的使用引起的，而这些问题是创造者没有预料到的。从那时起，我们一直在关注一个支持良好的 LMDB 的衍生产品，称为 MDBX，并希望使用他们的稳定性改进，并有可能在未来进行更多的合作。MDBX 的整合已经完成，现在是时候进行更多的测试和记录了。\n\u003e\n\u003e 从 LMDB 过渡到 MDBX 的好处：\n\u003e\n\u003e 1. 数据库文件的增长 \"空间(geometry)\" 工作正常。这一点很重要，尤其是在 Windows 上。在 LMDB 中，人们必须事先指定一次内存映射大小（目前我们默认使用 2Tb），如果数据库文件的增长超过这个限制，就必须重新启动这个过程。在 Windows 上，将内存映射大小设置为 2Tb 会使数据库文件一开始就有 2Tb 大，这不是很方便。在 MDBX 中，内存映射大小是以 2Gb 为单位递增的。这意味着偶尔的重新映射，但会带来更好的用户体验。\n\u003e\n\u003e 2. MDBX 对事务处理的并发使用有更严格的检查，以及在同一执行线程中的重叠读写事务。这使我们能够发现一些非明显的错误，并使行为更可预测。\n\u003e    在超过 5 年的时间里（自从它从 LMDB 中分离出来），MDBX 积累了大量的安全修复和 heisenbug 修复，据我们所知，这些修复仍然存在于 LMDB 中。其中一些是我们在测试过程中发现的，而 MDBX 的维护者也认真对待，并及时进行了修复。\n\u003e\n\u003e 3. 当涉及到不断修改数据的数据库时，它们会产生相当多的可回收空间（在 LMDB 术语中也被称为 \"freelist\"）。我们不得不给 LMDB 打上补丁，以修复在处理可回收空间时最严重的缺点 [（分析）](https://github.com/ledgerwatch/erigon/wiki/LMDB-freelist-illustrated-guide) 。[MDBX 对可回收空间的有效处理进行了特别的关注，到目前为止，还不需要打补丁。](https://github.com/ledgerwatch/erigon/wiki/LMDB-freelist-illustrated-guide%EF%BC%89%E3%80%82MDBX%E5%AF%B9%E5%8F%AF%E5%9B%9E%E6%94%B6%E7%A9%BA%E9%97%B4%E7%9A%84%E6%9C%89%E6%95%88%E5%A4%84%E7%90%86%E8%BF%9B%E8%A1%8C%E4%BA%86%E7%89%B9%E5%88%AB%E7%9A%84%E5%85%B3%E6%B3%A8%EF%BC%8C%E5%88%B0%E7%9B%AE%E5%89%8D%E4%B8%BA%E6%AD%A2%EF%BC%8C%E8%BF%98%E4%B8%8D%E9%9C%80%E8%A6%81%E6%89%93%E8%A1%A5%E4%B8%81%E3%80%82)\n\u003e\n\u003e 4. 根据我们的测试，MDBX 在我们的工作负载上表现得稍微好一些。\n\u003e\n\u003e 5. MDBX 暴露了更多的内部遥测数据 — 更多关于数据库内部发生的指标。而我们在 Grafana 中拥有这些数据 — 以便在应用设计上做出更好的决定。例如，在完全过渡到 MDBX 之后（移除对 LMDB 的支持），我们将实施 \"提交半满事务 \" 策略，以避免溢出/未溢出的磁盘接触。这将进一步简化我们的代码，而不影响性能。\n\u003e\n\u003e 6. MDBX 支持 \"Exclusive open \" 模式--我们将其用于数据库迁移，以防止任何其他读者在数据库迁移过程中访问数据库。\n\n## 关于\n\n本项目隶属于 **人民网络([rmw.link](//rmw.link))** 代码计划。\n\n\u003ca href=\"//rmw.link\"\u003e ![人民网络](https://raw.githubusercontent.com/rmw-link/logo/master/rmw.red.bg.svg) \u003c/a\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frmw-lib%2Fmdbx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frmw-lib%2Fmdbx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frmw-lib%2Fmdbx/lists"}