LMDB 键值数据库介绍

发布时间: 更新时间: 总字数:1991 阅读时间:4m 作者: IP上海 分享 网址

LMDB (Lightning Memory-Mapped Database) 是一个嵌入式、事务性的 键-值存储 (Key-Value Store) 数据库库,它以其高性能、零拷贝 (Zero-Copy) 和高可靠性而闻名。下面介绍 LMDB 的文件格式和主要特性。

LMDB 文件格式概述

LMDB 通常使用两个文件来持久化数据和管理并发访问:

  1. data.mdb:

    • 这是主要的数据文件,包含了所有的键-值数据。
    • 它的结构是基于 B+ 树 (B+ tree) 的。
    • 整个文件被内存映射 (Memory-Mapped) 到进程的地址空间,这是其高性能的关键,数据读取可以直接通过内存访问,无需额外的系统调用或数据拷贝(零拷贝)。
    • 文件内部被组织成固定大小的页 (Pages),通常大小与操作系统的内存页大小(例如 4KB)一致。
  2. lock.mdb:

    • 这是一个锁文件,用于在多个进程或线程之间协调并发访问,特别是写事务。
    • 它包含元数据和锁定信息。

LMDB 的核心特性

内存映射 (Memory-Mapped)

  • 高效访问: 数据库文件被直接映射到内存。读取数据时,直接从映射的内存中返回数据,避免了磁盘 I/O 和内存之间的 malloc/memcpy 操作,从而实现极高的读取性能。

B+ 树结构

  • 所有键-值对都存储在一个或多个 B+ 树中,提供了范围查询 (Range-based search) 的能力,并且键是按序存储的(有序映射)。

事务和 ACID 保证 (Transaction and ACID)

  • LMDB 提供了完全的事务性,具有 ACID(原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability)特性。
  • 写操作是序列化的,同一时间只允许一个写事务处于活跃状态。

多版本并发控制 (MVCC)

  • LMDB 使用 MVCC 机制:
    • 读事务无锁的,并且永远不会阻塞写事务,写事务也不会阻塞读事务。
    • 读者看到的是数据库的一致性快照。读取性能可以随着 CPU 数量的增加而线性扩展。

写时复制 (Copy-on-Write)

  • 进行数据修改时,LMDB 采用写时复制 (Copy-on-Write) 策略,它永远不会覆盖正在使用的数据页。
  • 这种设计保证了磁盘上的结构始终有效,即使系统或应用崩溃,数据库也不会处于损坏状态,无需特殊恢复过程

可移植性限制

  • 重要提示: LMDB 的文件格式是依赖于体系结构的(例如 32 位/64 位、字节序)。因此,将数据库从一台机器迁移到另一台不同架构的机器时,通常需要先进行导出/导入操作。

示例

以下是一个完整的 Python 示例,展示了如何使用 lmdb 库进行创建、读取、修改等基本操作。

  • 安装
pip install lmdb
  • example_lmdb.py
import lmdb
import os

# --- 1. 环境准备与初始化 ---
# 定义数据库存储的目录
db_path = './my_lmdb'

# 确保目录存在
os.makedirs(db_path, exist_ok=True)

# 打开或创建 LMDB 环境
# map_size 指定了数据库的最大大小,根据需要调整
env = lmdb.open(db_path, map_size=1024*1024*10)  # 10 MB

print(f"数据库已打开/创建,路径: {db_path}")

# --- 2. 写入数据 (Put) ---
print("\n--- 写入数据 ---")
# 使用 write 事务来修改数据库
with env.begin(write=True) as txn:
    # put(key, value) 将键值对存入数据库
    # 注意:key 和 value 都必须是 bytes 类型
    txn.put(b'key1', b'value1')
    txn.put(b'key2', b'value2')
    txn.put(b'key3', b'value3')
    print("已写入 3 条记录: key1->value1, key2->value2, key3->value3")

# --- 3. 读取数据 (Get) ---
print("\n--- 读取数据 ---")
# 使用 read 事务来读取数据库
with env.begin() as txn:
    # get(key) 获取指定 key 的 value
    value = txn.get(b'key2')
    print(f"读取 'key2': {value}")

    # 尝试获取一个不存在的 key
    non_existent_value = txn.get(b'key_does_not_exist')
    print(f"读取 'key_does_not_exist': {non_existent_value}")

# --- 4. 修改数据 ---
print("\n--- 修改数据 ---")
# 修改数据与写入数据使用相同的方法 put()
with env.begin(write=True) as txn:
    # 如果 key 存在,put 会覆盖旧的 value
    txn.put(b'key2', b'new_value_for_key2')
    print("已修改 'key2' 的值为 'new_value_for_key2'")

# 验证修改
with env.begin() as txn:
    new_value = txn.get(b'key2')
    print(f"再次读取 'key2' 验证修改: {new_value}")

# --- 5. 遍历数据 (Iterate) ---
print("\n--- 遍历所有数据 ---")
# 使用 cursor 来遍历数据库中的所有键值对
with env.begin() as txn:
    cursor = txn.cursor()
    for key, value in cursor:
        print(f"  键: {key}, 值: {value}")

# --- 6. 删除数据 (Delete) ---
print("\n--- 删除数据 ---")
with env.begin(write=True) as txn:
    # delete(key) 删除指定的 key-value 对
    deleted = txn.delete(b'key1')
    if deleted:
        print("成功删除 'key1'")
    else:
        print("'key1' 不存在,无法删除")

# 验证删除
with env.begin() as txn:
    remaining_value = txn.get(b'key1')
    print(f"尝试读取已删除的 'key1': {remaining_value}")

# --- 7. 统计信息 ---
print("\n--- 数据库统计信息 ---")
with env.begin() as txn:
    stat = env.stat()
    print(f"数据库大小 (条目数): {stat['entries']}")

# --- 8. 清理与关闭 ---
print("\n--- 清理与关闭 ---")
# 关闭环境以释放资源
env.close()
print("数据库环境已关闭。")

# (可选) 删除数据库文件夹以清理
# import shutil
# shutil.rmtree(db_path)
# print(f"临时数据库 '{db_path}' 已被删除。")

代码解释

  1. import lmdbos: 导入 lmdb 库进行数据库操作,导入 os 库用于处理文件系统路径。
  2. db_pathos.makedirs: 定义一个本地文件夹作为数据库存储位置,并使用 os.makedirs 确保该文件夹存在。
  3. lmdb.open: 这是打开或创建 LMDB 数据库环境的关键函数。map_size 参数非常重要,它定义了数据库文件能增长到的最大字节数。如果数据量超过此大小,写入操作会失败。
  4. env.begin(write=True): 开始一个写事务。任何修改数据库(put, delete)的操作都必须在写事务中进行。使用 with 语句可以确保事务在代码块结束时自动提交或回滚,即使发生异常也能保证资源被正确释放。
  5. txn.put(key, value): 将一个键值对存入数据库。注意keyvalue 都必须是 bytes 类型,不能是 str。如果需要存储字符串,需要用 .encode() 方法转换。
  6. env.begin() (无 write=True): 开始一个只读事务。用于读取数据(get, cursor 遍历)。
  7. txn.get(key): 根据 key 获取对应的 value。如果 key 不存在,则返回 None
  8. txn.cursor(): 创建一个游标对象,用于高效地遍历数据库中的所有键值对。
  9. txn.delete(key): 删除指定的 key 及其对应的 value。如果 key 存在,返回 True;如果不存在,返回 False
  10. env.stat(): 获取数据库的统计信息,如条目总数。
  11. env.close(): 在程序结束前,务必调用 env.close() 来关闭数据库环境,释放所有相关资源。
本文总阅读量 次 本站总访问量 次 本站总访客数
Home Archives Categories Tags Statistics