在 SQLite 中使用 UUID 扩展

为什么使用UUID

UUID (Universally Unique Identifier) 作为主键在数据库中有几个显著优势,特别是在 SQLite 中使用 BLOB 类型存储时:

UUID 的主要优势

  1. 全局唯一性
    • 几乎可以保证在不同时间、不同机器上生成的ID都是唯一的
    • 避免了分布式系统中的ID冲突问题
  2. 安全性
    • 比自增整数更难猜测,减少信息泄露风险
    • 适用于需要隐藏数据规模的场景
  3. 离线生成
    • 客户端可以在不连接数据库的情况下生成有效的ID
    • 适合离线应用或同步场景
  4. 无中心化需求
    • 不需要中央服务器分配ID,适合分布式系统

在 SQLite 中使用 BLOB 存储 UUID 的优势

  1. 存储效率
    • BLOB(16字节)比文本表示(36字符)更紧凑
    • 节省存储空间,特别是大量记录时
  2. 性能优势
    • 二进制比较比字符串比较更快
    • 索引效率更高
  3. 一致性
    • 避免文本表示的大小写和格式问题

使用 Run-Time Loadable Extensions 使 SQLite3 支持 UUID

编译 .so 共享库

  1. 下载 uuid.c https://www.sqlite.org/src/file?name=ext/misc/uuid.c

  2. 编译成共享库(shared library file)并移动到/usr/local/lib(可选)

    gcc -fPIC -shared uuid.c -o uuid.so
    sudo mkdir -p /usr/local/lib/sqlite3
    sudo cp uuid.so /usr/local/lib/sqlite3/uuid.so 

sqlite3 命令行中使用

  1. (可选)修改 ~/.sqliterc 使在使用 sqlite3 命令行时自动加载扩展

    echo ".load /usr/local/lib/sqlite3/uuid" >> ~/.sqliterc
  2. 检查扩展功能

    $ sqlite3
    -- Loading resources from /home/duzhuo/.sqliterc
    SQLite version 3.45.1 2024-01-30 16:01:20
    Enter ".help" for usage hints.
    Connected to a transient in-memory database.
    Use ".open FILENAME" to reopen on a persistent database.
    sqlite> .load /usr/local/lib/sqlite3/uuid
    sqlite> SELECT uuid();
    65a54293-036f-4aec-869b-2dcdcd523555
    sqlite> 

在 Python 标准库中使用 SQLite 加载扩展

注意:Python 默认禁用 SQLite 扩展加载(安全原因)。如需启用,必须重新编译 Python 并加上 –enable-loadable-sqlite-extensions 选项。

产生的报错信息 :

AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'
AttributeError: 'sqlite3.Connection' object has no attribute 'load_extension'

这里以 pyenv 重新安装示例:

$ PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" pyenv install 3.13
pyenv: /home/duzhuo/.pyenv/versions/3.13.3 already exists
continue with installation? (y/N) y
Installing Python-3.13.3...
Installed Python-3.13.3 to /home/duzhuo/.pyenv/versions/3.13.3
$ python3
Python 3.13.3 (main, Jun 28 2025, 14:46:43) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> con = sqlite3.connect(":memory:")
>>> con.enable_load_extension(True)
>>> con.load_extension("/usr/local/lib/sqlite3/uuid.so")
>>> con.close()

UUID 扩展的 Python 使用示例:

import sqlite3
from sqlite3 import Cursor, Connection
from typing import Dict, Optional


def init_db() -> Connection:
    """初始化数据库并加载 UUID 扩展"""
    conn: Connection = sqlite3.connect(":memory:")
    # 启用字典游标,使fetch不返回元组返回字典
    conn.row_factory = sqlite3.Row

    # 加载扩展(路径需根据实际情况调整)
    conn.enable_load_extension(True)
    conn.load_extension("/usr/local/lib/sqlite3/uuid")  # 或 .dll

    # 创建测试表
    conn.execute(
        """
    CREATE TABLE items (
        id BLOB PRIMARY KEY,  -- 存储为 16 字节 BLOB
        name TEXT
    )
    """
    )
    return conn


def demo_uuid_operations(conn: Connection) -> None:
    """演示纯 SQLite UUID 扩展操作"""
    cursor: Cursor = conn.cursor()

    # 插入数据(完全使用 SQLite 函数)
    cursor.execute(
        """
    INSERT INTO items (id, name)
    VALUES 
        (uuid_blob(uuid()), 'Item1'),  -- 生成随机 UUID 并转为 BLOB
        (uuid_blob('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), 'Item2')  -- 从字符串转换
    """
    )

    # 查询并格式化 UUID
    query_statement: str = """
    SELECT 
        name, 
        uuid_str(id) AS uuid  -- 将 BLOB 转为标准字符串
    FROM items
    """

    print("\n所有物品:")
    for row in cursor.execute(query_statement).fetchall():
        print(row['name'], row['uuid'])

    # 按 UUID 查询
    input_uuid: str = "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"  # 支持各种格式
    cursor.execute(
        """
    SELECT name FROM items 
    WHERE id = uuid_blob(?)
    """,
        (input_uuid,),
    )
    result: Optional[Dict[str, str]] = cursor.fetchone()
    print(f"\n查询 UUID '{input_uuid}' 的结果:{result["name"] if result else '未找到'}")


if __name__ == "__main__":
    db: Connection = init_db()
    demo_uuid_operations(db)
    db.close()