TinyDB轻量文档数据库

目录

  • 简介
  • 快速开始
    • [1. 创建数据库](#1. 创建数据库)
    • [2. 插入数据](#2. 插入数据)
    • [3. 查询全部数据](#3. 查询全部数据)
    • [4. 遍历数据](#4. 遍历数据)
    • [5. 条件查询](#5. 条件查询)
    • [6. 更新数据](#6. 更新数据)
    • [7. 删除数据](#7. 删除数据)
    • [8. 清空数据库](#8. 清空数据库)
    • 速查表
    • 注意事项
  • 进阶用法
    • 查询操作
    • 数据处理
      • [Inserting data(插入数据)](#Inserting data(插入数据))
      • [Updating data(更新数据)](#Updating data(更新数据))
    • 数据访问与修改
      • [Upserting data(插入或更新)](#Upserting data(插入或更新))
      • [Retrieving data(数据获取)](#Retrieving data(数据获取))
        • [1. 获取文档数量](#1. 获取文档数量)
        • [2. 查询数据(search)](#2. 查询数据(search))
        • [3. 获取单条数据(get)](#3. 获取单条数据(get))
        • [4. 判断是否存在(contains)](#4. 判断是否存在(contains))
        • [5. 统计匹配数量(count)](#5. 统计匹配数量(count))
      • 速查表
    • [使用文档 ID](#使用文档 ID)
    • [Storage & Middleware(存储与中间件)](#Storage & Middleware(存储与中间件))
    • 类型检查
  • [扩展 TinyDB](#扩展 TinyDB)
  • 结尾(Conclusion)

简介

为什么使用 TinyDB?

tiny(轻量)

目前源码大约只有 1800 行(其中约 40% 是文档),以及 1600 行测试代码。

面向文档(document oriented)

类似 MongoDB,你可以在 TinyDB 中存储任意文档(以 dict 形式表示)。

以开发体验为核心(optimized for your happiness)

TinyDB 的设计目标是简单且易用,提供干净、直观的 API,让使用过程更加轻松愉快。

纯 Python 实现(written in pure Python)

TinyDB 不需要外部服务(例如 PyMongo 依赖 MongoDB 服务),也不依赖 PyPI 上的其他包。

兼容 Python 3.5+ 与 PyPy

可以运行在所有现代 Python 版本以及 PyPy 上。

高度可扩展(powerfully extensible)

你可以通过编写自定义存储(storage)或使用中间件(middleware)来轻松扩展 TinyDB 的功能或修改其行为。

100% 测试覆盖率

无需多言。

如果你需要一个无需复杂配置、开箱即用、API 简洁的轻量数据库,TinyDB 可能是一个不错的选择。

但,如果你有以下需求,那么 TinyDB 并不适合你:

  • 多进程或多线程访问(例如在 Flask 场景中)
  • 表索引(index)
  • HTTP 服务接口
  • 表之间的关系管理(类似关系型数据库)
  • ACID 事务保证

或者:

👉 你对性能要求非常高,需要一个高速数据库

快速开始

只需要运行:

python 复制代码
pip install tinydb

在深入细节之前,我们先快速了解一下 TinyDB 的基本使用方式。

1. 创建数据库

python 复制代码
from tinydb import TinyDB, Query

db = TinyDB('db.json')

现在你已经创建了一个 TinyDB 数据库,它会将数据存储在 db.json 文件中。

2. 插入数据

TinyDB 以 Python 的 dict 作为数据存储格式:

python 复制代码
db.insert({'type': 'apple', 'count': 7})
db.insert({'type': 'peach', 'count': 3})

💡 注意
insert 方法会返回插入文档的 ID(Document ID)。

3. 查询全部数据

python 复制代码
db.all()

输出:

python 复制代码
[
    {'count': 7, 'type': 'apple'},
    {'count': 3, 'type': 'peach'}
]

4. 遍历数据

python 复制代码
for item in db:
    print(item)

输出:

python 复制代码
{'count': 7, 'type': 'apple'}
{'count': 3, 'type': 'peach'}

5. 条件查询

python 复制代码
Fruit = Query()

db.search(Fruit.type == 'peach')
# [{'count': 3, 'type': 'peach'}]

db.search(Fruit.count > 5)
# [{'count': 7, 'type': 'apple'}]

6. 更新数据

python 复制代码
db.update({'count': 10}, Fruit.type == 'apple')

db.all()

结果:

python 复制代码
[
    {'count': 10, 'type': 'apple'},
    {'count': 3, 'type': 'peach'}
]

7. 删除数据

python 复制代码
db.remove(Fruit.count < 5)

db.all()

结果:

python 复制代码
[
    {'count': 10, 'type': 'apple'}
]

8. 清空数据库

python 复制代码
db.truncate()

db.all()
# []

速查表

在继续深入之前,我们先总结TinyDB 的基础用法:

分类 用法 说明
插入(Inserting) db.insert(...) 插入一条文档
查询(Getting Data) db.all() 获取所有文档
iter(db) 遍历所有文档
db.search(query) 获取符合条件的文档列表
更新(Updating) db.update(fields, query) 更新所有匹配条件的文档
删除(Removing) db.remove(query) 删除所有匹配条件的文档
db.truncate() 删除所有文档(清空数据库)
查询构造(Querying) Query() 创建一个查询对象
Query().field == 2 匹配 field == 2 的文档(也支持 !=, >, >=, <, <=

TinyDB 的核心操作其实就围绕这四类:CRUD + Query 构造,没有额外抽象层,这也是它"简单"的本质。

注意事项

1. 查询限制

Query 只支持右侧为"字面量"的比较,例如:

python 复制代码
Query().a == 1  # ✅ 支持
Query().a == Query().b  # ❌ 不支持

如果需要字段之间比较,可以使用自定义函数:

python 复制代码
db.search(lambda doc: doc.get('a') == doc.get('b'))

2. 安全性提醒

传入查询 API 的函数(例如 lambdaQuery().map)是在当前进程中执行的:

👉 绝对不要使用来自用户输入的不可信代码

否则可能带来安全风险。

3. TinyDB查询本质

TinyDB 的查询看起来很像 ORM,但实际上是基于 Python 表达式构建的,这也决定了它在灵活性和限制之间的权衡。

TinyDB 默认情况下基本就是"全量扫描 + Python 条件过滤",而不是像 SQLite 那样做索引优化和查询计划。

像你写的:

python 复制代码
db.search(Fruit.count > 5)

本质上会变成类似:

python 复制代码
for doc in 所有数据:
    if 条件表达式(doc):
        收集结果

也就是说:

  • Query() 构建的是一个可执行的条件对象
  • 最终是在 Python 层面对每条数据执行判断
  • 没有查询优化器
  • 没有索引(默认情况下)

为什么它看起来"像 ORM"?

因为写法是:

python 复制代码
Fruit.count > 5

这种 DSL 很像 ORM(比如 Django ORM / SQLAlchemy)

但本质区别是:

对比点 TinyDB ORM(+ SQL数据库)
执行位置 Python 层 数据库引擎
查询方式 全表扫描 索引 + 执行计划
表达式 Python 对象 SQL AST
性能 O(n) 可优化

正因为它是在 Python 层执行,所以你可以写:

python 复制代码
db.search(lambda doc: doc['a'] == doc['b'])

甚至更复杂逻辑:

python 复制代码
db.search(lambda doc: doc['count'] % 2 == 0 and len(doc['type']) > 3)

👉 这在 SQLite 里反而是麻烦甚至不可能直接表达的(需要写函数 / 扩展)。

带来的问题就是:

  • 数据量一大 → 性能线性下降
  • 并发 → 基本不支持
  • 复杂查询 → 全靠你自己写逻辑

TinyDB 并不是"数据库在执行查询",而是"Python 在帮你筛数据"。

这让它极其灵活,但也天然放弃了索引、优化器和高性能。

它其实连"数据库"都算不上,更像一个可持久化的 dict + 查询引擎

进阶用法

默认情况下,TinyDB 使用 Python 内置的 JSON 模块 将数据序列化并写入磁盘。

  • 优点:简单、直观,适合基础数据类型
  • 缺点:无法处理复杂类型(例如自定义类)

此外,在 Python 2 中读取数据时还会涉及字符串转 Unicode 的问题(历史遗留问题)。

👉 如果这些限制对你有影响,你可以:

  • 自定义 Storage
  • 使用更强大的序列化库(例如 picklePyYAML)(但通常会更慢)

💡 提示(Hint)

在同一份数据上打开多个 TinyDB 实例(例如使用 JSONStorage)可能会因为**查询缓存(query cache)**导致异常行为。

可以通过关闭 query cache 来避免这个问题。

查询操作

TinyDB 提供了两种主要的查询构造方式。

第一种方式类似 ORM 风格:

python 复制代码
from tinydb import Query

User = Query()
db.search(User.name == 'John')

可以像访问属性一样访问字段,也支持嵌套字段:

python 复制代码
db.search(User.birthday.year == 1990)

如果字段名不是合法的 Python 标识符,则需要使用字典方式访问:

python 复制代码
db.search(User['country-code'] == 'foo')

此外,还可以在字段上应用转换函数:

python 复制代码
from unidecode import unidecode
db.search(User.name.map(unidecode) == 'Jose')

第二种是更传统的写法:

python 复制代码
from tinydb import where

db.search(where('field') == 'value')

它本质上只是 Query()['field'] 的简写形式,同样支持嵌套字段访问。

高级查询

在 Getting Started 中你已经了解了基础比较操作(==, <, >, ...)。在此基础上,TinyDB 还支持以下查询方式:

python 复制代码
# 字段存在性判断:
db.search(User.name.exists())

# 正则匹配:
# 整个字段必须匹配正则:
db.search(User.name.matches('[aZ]*'))

# 忽略大小写匹配 'John':
import re
db.search(User.name.matches('John', flags=re.IGNORECASE))

# 字段中任意部分匹配正则:
db.search(User.name.search('b+'))

# 自定义测试函数:
test_func = lambda s: s == 'John'
db.search(User.name.test(test_func))

# 带参数的自定义测试:
def test_func(val, m, n):
    return m <= val <= n

db.search(User.age.test(test_func, 0, 21))
db.search(User.age.test(test_func, 21, 99))

另一种常见场景是:当你有一个 dict,希望查找所有包含该结构的文档,这种方式称为 fragment 查询(片段匹配)

python 复制代码
db.search(Query().fragment({'foo': True, 'bar': False}))
[{'foo': True, 'bar': False, 'foobar: 'yes!'}]

你也可以限定在某个字段上进行 fragment 匹配:

python 复制代码
db.search(Query().field.fragment({'foo': True, 'bar': False}))
[{'field': {'foo': True, 'bar': False, 'foobar: 'yes!'}]

当字段是列表时,可以使用 anyall 方法。有两种使用方式:基于值列表或基于嵌套查询。先看第一种。

假设有如下数据:

python 复制代码
db.insert({'name': 'user1', 'groups': ['user']})
db.insert({'name': 'user2', 'groups': ['admin', 'user']})
db.insert({'name': 'user3', 'groups': ['sudo', 'user']})

可以这样查询:

python 复制代码
# 用户的 groups 至少包含 ['admin', 'sudo'] 中的一个
db.search(User.groups.any(['admin', 'sudo']))
[{'name': 'user2', 'groups': ['admin', 'user']},
 {'name': 'user3', 'groups': ['sudo', 'user']}]

# 用户的 groups 必须同时包含 ['admin', 'user']
db.search(User.groups.all(['admin', 'user']))
[{'name': 'user2', 'groups': ['admin', 'user']}]

在更复杂的场景中,可以使用嵌套查询 来配合 any / all

python 复制代码
Group = Query()
Permission = Query()
groups = db.table('groups')

groups.insert({
        'name': 'user',
        'permissions': [{'type': 'read'}]})
groups.insert({
        'name': 'sudo',
        'permissions': [{'type': 'read'}, {'type': 'sudo'}]})
groups.insert({
        'name': 'admin',
        'permissions': [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]})

查询示例:

python 复制代码
# group 至少有一个 'read' 权限
groups.search(Group.permissions.any(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]},
 {'name': 'sudo', 'permissions': [{'type': 'read'}, {'type': 'sudo'}]},
 {'name': 'admin', 'permissions':
        [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]}]

# group 只包含 'read' 权限
groups.search(Group.permissions.all(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]}]

可以看到:

  • any:只要存在一个匹配即可
  • all:必须全部匹配

相反的操作(判断字段值是否属于某个列表)可以使用 one_of

python 复制代码
db.search(User.name.one_of(['jane', 'john']))

查询组合

TinyDB 支持使用逻辑运算来组合查询:

python 复制代码
# 取反(NOT)
db.search(~ (User.name == 'John'))

# 与(AND)
db.search((User.name == 'John') & (User.age <= 30))

# 或(OR)
db.search((User.name == 'John') | (User.name == 'Bob'))

⚠️ 注意

  • 使用 &| 时,必须用括号包裹两边的表达式
  • 使用 ~ 时,也必须用括号包裹整个表达式

原因是:Python 中这些运算符的优先级高于比较运算符。例如:

python 复制代码
~ User.name == 'John'

会被解析为:

python 复制代码
(~User.name) == 'John'

而不是:

python 复制代码
~(User.name == 'John')

可以使用空查询来动态构造条件:

python 复制代码
Query().noop()

限制与注意事项

比较操作只支持右侧为字面量:

python 复制代码
Query().a == Query().b  # ❌ 不支持

如果需要字段之间比较,可以使用函数:

python 复制代码
db.search(lambda doc: doc.get('a') == doc.get('b'))

⚠️ 安全提醒

所有传入查询 API 的函数(如 lambdaQuery().mapQuery().test):

👉 都是在当前进程中执行

👉 绝不能来自不可信输入

速查表

查询能力

操作 说明
Query().field.exists() 匹配包含该字段的文档
Query().field.matches(regex) 字段完全匹配正则
Query().field.search(regex) 字段部分匹配正则
Query().field.test(func, *args) 自定义函数判断
`Query().field.all(query list)`
`Query().field.any(query list)`
Query().field.one_of(list) 字段值属于列表

逻辑运算

操作 说明
~ (query) 逻辑非(NOT)
(query1) & (query2) 逻辑与(AND)
`(query1) (query2)`

数据处理

Inserting data(插入数据)

如前所述,可以使用 db.insert(...) 插入单条文档。如果需要插入多条数据,可以使用 db.insert_multiple(...)

python 复制代码
db.insert_multiple([
        {'name': 'John', 'age': 22},
        {'name': 'John', 'age': 37}])

db.insert_multiple({'int': 1, 'value': i} for i in range(2))

在某些情况下,你可能希望手动指定文档 ID ,可以通过 Document 类实现:

python 复制代码
db.insert(Document({'name': 'John', 'age': 22}, doc_id=12))
12

db.insert_multiple(...) 同样支持:

python 复制代码
db.insert_multiple([
    Document({'name': 'John', 'age': 22}, doc_id=12),
    Document({'name': 'Jane', 'age': 24}, doc_id=14),
])
[12, 14]

⚠️ 注意

如果插入的 Document 的 ID 已存在,会抛出 ValueError

Updating data(更新数据)

有时你希望更新数据库中的所有文档,这时可以省略查询条件:

python 复制代码
db.update({'foo': 'bar'})

当你向 db.update(fields, query) 传入一个 dict 时,只能进行字段新增或覆盖

如果你需要更复杂的操作(例如删除字段或修改数值),可以传入一个函数:

python 复制代码
from tinydb.operations import delete

db.update(delete('key1'), User.name == 'John')

这会从所有匹配的文档中删除 key1 字段。

TinyDB 内置了一些常用操作:

  • delete(key):删除字段
  • increment(key):字段值递增
  • decrement(key):字段值递减
  • add(key, value):为字段增加值(字符串也支持)
  • subtract(key, value):字段值减少
  • set(key, value):设置字段值

当然,你也可以自定义操作:

python 复制代码
def your_operation(your_arguments):
    def transform(doc):
        # do something with the document
        # ...
    return transform

db.update(your_operation(arguments), query)

如果需要一次执行多个更新操作,可以使用 update_multiple

python 复制代码
db.update_multiple([
    ({'int': 2}, where('char') == 'a'),
    ({'int': 4}, where('char') == 'b'),
])

也可以混合普通更新和操作函数:

python 复制代码
db.update_multiple([
    ({'int': 2}, where('char') == 'a'),
    ({delete('int'), where('char') == 'b'),
])

TinyDB 的数据操作本质上是"对文档做函数式变换",而不是传统数据库的 SQL 更新语义。

数据访问与修改

Upserting data(插入或更新)

在某些场景下,你可能需要"更新 + 插入"二合一的操作:upsert(存在则更新,不存在则插入)

该操作需要提供一个 document 和一个 query:

python 复制代码
db.upsert({'name': 'John', 'logged-in': True}, User.name == 'John')

含义是:

  • 如果存在 name == John 的文档 → 更新这些文档
  • 如果不存在 → 插入新文档

如果你希望使用 document ID 作为匹配条件 ,可以直接传入 Document

python 复制代码
db.upsert(Document({'name': 'John', 'logged-in': True}, doc_id=12))

Retrieving data(数据获取)

TinyDB 提供多种数据读取方式。

1. 获取文档数量
python 复制代码
len(db)

输出示例:

复制代码
3

💡 提示

这个值表示默认 table 中的文档数量(见 default table 相关说明)。

2. 查询数据(search)

最基础方式仍然是:

python 复制代码
db.search(User.name == 'John')

但如果你只想要单条结果 ,可以使用 db.get(...)

3. 获取单条数据(get)

传统写法:

python 复制代码
try:
    result = db.search(User.name == 'John')[0]
except IndexError:
    pass

更简洁方式:

python 复制代码
db.get(User.name == 'John')

返回结果:

复制代码
{'name': 'John', 'age': 22}

如果没有匹配项:

复制代码
None

⚠️ 注意

如果多个文档匹配 query,返回的结果可能是其中任意一个(不保证顺序)。

4. 判断是否存在(contains)

如果你只关心"是否存在",可以使用:

python 复制代码
db.contains(User.name == 'John')
5. 统计匹配数量(count)
python 复制代码
db.count(User.name == 'John')

输出示例:

复制代码
2

速查表

操作 说明
db.insert_multiple(...) 插入多条文档
db.update(operation, ...) 使用操作函数更新文档
len(db) 获取文档总数
db.get(query) 获取一条匹配文档
db.contains(query) 判断是否存在匹配文档
db.count(query) 统计匹配文档数量

📝 Note
upsert 是 TinyDB v3.6.0 引入的新特性。

使用文档 ID

在 TinyDB 内部,每一条插入的数据都会自动分配一个唯一 ID。

插入时返回 ID:

python 复制代码
db.insert({'name': 'John', 'age': 22})
3

db.insert_multiple([{...}, {...}, {...}])
[4, 5, 6]

获取已存在文档的 ID:

可以通过 document.doc_id 获取:

python 复制代码
el = db.get(User.name == 'John')
el.doc_id
3
python 复制代码
el = db.all()[0]
el.doc_id
1
python 复制代码
el = db.all()[-1]
el.doc_id
12

基于 ID 的操作:

以下方法都支持直接使用 ID:

  • update
  • remove
  • contains
  • get
python 复制代码
db.update({'value': 2}, doc_ids=[1, 2])
db.contains(doc_id=1)
True
db.remove(doc_ids=[1, 2])
db.get(doc_id=3)
{...}
db.get(doc_ids=[1, 2])
[{...}, {...}]

👉 使用 doc_id / doc_ids 相比 Query()

  • 速度更快
  • 直接定位文档(无需扫描)

速查表

获取 ID

操作 说明
db.insert(...) 返回插入文档 ID
db.insert_multiple(...) 返回多个 ID
document.doc_id 获取文档 ID

基于 ID 的操作

操作 说明
db.get(doc_id=...) 按 ID 获取文档
db.contains(doc_id=...) 判断 ID 是否存在
db.update({...}, doc_ids=[...]) 按 ID 更新
db.remove(doc_ids=[...]) 按 ID 删除

TinyDB 支持多表结构,每个表的行为与数据库对象一致。

创建表:

python 复制代码
table = db.table('table_name')
table.insert({'value': True})
table.all()
[{'value': True}]

遍历表:

python 复制代码
for row in table:
    print(row)

删除表

python 复制代码
db.drop_table('table_name')

删除所有表

python 复制代码
db.drop_tables()

获取所有表名

python 复制代码
db.tables()
{'_default', 'table_name'}

Default Table(默认表)

TinyDB 使用 _default 作为默认表。

所有直接操作数据库对象的方法:

python 复制代码
db.insert(...)

都会作用于默认表。

修改默认表名:

方式 1:仅当前实例

python 复制代码
db = TinyDB(storage=SomeStorage)
db.default_table_name = 'my-default'

方式 2:全局修改

python 复制代码
TinyDB.default_table_name = 'my-default'

Query Caching(查询缓存)

TinyDB 会缓存查询结果以提升性能:

  • 相同查询不会重复读取存储层
  • 只要数据库未变化,直接复用结果

设置缓存大小:

python 复制代码
table = db.table('table_name', cache_size=30)

缓存配置说明:

  • cache_size = None → 无限制缓存
  • cache_size = 0 → 禁用缓存

注意事项:

  • 不支持对同一表使用不同 cache 配置
  • 第一次创建后,后续调用会复用相同配置

外部修改数据风险

缓存不会检测外部进程修改数据:

👉 可能导致读取到旧数据

解决方式:

python 复制代码
db.clear_cache()

内存注意事项

当使用:

  • 无限缓存
  • test() / lambda 查询

可能导致:

👉 长期运行应用出现内存泄漏(因为函数引用未释放)

Storage & Middleware(存储与中间件)

Storage Types(存储类型)

TinyDB 内置两种存储方式:JSON 存储内存存储

默认情况下,数据会被写入 JSON 文件,因此需要指定文件路径:

python 复制代码
from tinydb import TinyDB, where
db = TinyDB('path/to/db.json')

如果你不需要持久化,可以使用内存存储:

python 复制代码
from tinydb.storages import MemoryStorage
db = TinyDB(storage=MemoryStorage)

除了 storage 参数外,其他参数都会被传递给底层 storage。

例如 JSON storage 支持传入 json.dumps(...) 的参数:

python 复制代码
db = TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': '))

JSONStorage 会将 **kwargs 透传给 json.dumps

⚠️ 不要传入用户可控的 callable 参数(如 default / cls

因为它们会在写入时在进程内执行,存在安全风险。

可以全局修改默认 storage:

python 复制代码
TinyDB.default_storage_class = MemoryStorage

有时你可能需要直接操作 storage:

python 复制代码
db = TinyDB(storage=CachingMiddleware(MemoryStorage))
db.storage.flush()

Middleware(中间件)

Middleware 用于包装 storage,从而扩展其行为。

python 复制代码
from tinydb.storages import JSONStorage
from tinydb.middlewares import CachingMiddleware

db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))

可以多层组合:

python 复制代码
db = TinyDB('/path/to/db.json',
            storage=FirstMiddleware(SecondMiddleware(JSONStorage)))

TinyDB 提供的 CachingMiddleware 用于提升性能:

  • 减少磁盘 IO
  • 缓存读取结果
  • 延迟写入磁盘

关闭数据库时,确保数据写入磁盘:

python 复制代码
# 使用上下文管理器
with database as db:
    # your operations
python 复制代码
# 或手动关闭
db.close()

类型检查

TinyDB 提供类型注解,可用于 MyPy 静态检查。

但由于部分实现模式较复杂,MyPy 无法完全理解,因此提供了 plugin 支持。

启用 MyPy 插件:

在配置文件(mypy.inisetup.cfg)中添加:

ini 复制代码
[mypy]
plugins = tinydb.mypy_plugin

扩展 TinyDB

扩展 TinyDB 主要有四种方式:

  • 自定义 storage
  • 自定义 middleware
  • 使用 hooks 和 overrides
  • 继承 TinyDB / Table

编写自定义存储

TinyDB 默认提供两种存储:

  • 内存存储
  • JSON 文件存储

但你也可以实现自己的 storage,比如使用 PyYAML 实现 YAML 存储:

python 复制代码
import yaml

class YAMLStorage(Storage):
    def __init__(self, filename):  # (1)
        self.filename = filename

    def read(self):
        with open(self.filename) as handle:
            try:
                data = yaml.safe_load(handle.read())  # (2)
                return data
            except yaml.YAMLError:
                return None  # (3)

    def write(self, data):
        with open(self.filename, 'w+') as handle:
            yaml.dump(data, handle)

    def close(self):  # (4)
        pass

需要注意的几点:

(1) 构造函数参数来源

构造函数会接收创建 TinyDB 实例时传入的参数(除了 storage 本身)。

例如:

python 复制代码
TinyDB('something', storage=YAMLStorage)

会把 'something' 传给 YAMLStorage

⚠️ 如果你的 storage 接收可执行对象(如 callable),不要使用来自不可信输入的数据。

(2) 安全解析 YAML

这里使用:

python 复制代码
yaml.safe_load

这是 PyYAML 推荐的安全方式,用于处理不可信数据源。

(3) 初始化返回 None

如果 storage 还没有初始化,TinyDB 期望返回:

python 复制代码
None

以便框架执行内部初始化逻辑。

(4) close 方法

如果 storage 需要资源清理(比如关闭文件句柄),可以写在 close()

python 复制代码
def close(self):
    pass

关闭方式:

python 复制代码
with TinyDB('db.yml', storage=YAMLStorage) as db:
    # ...

或者:

python 复制代码
db.close()

使用 YAML Storage,使用方式非常直接:

python 复制代码
db = TinyDB('db.yml', storage=YAMLStorage)
# ...

编写自定义中间件

有时候你不需要重新实现一个 storage,而只是想修改已有 storage 的行为

例如,这里我们实现一个 middleware,用来过滤掉空数据项。

在 TinyDB 中,middleware 本质上是对 storage 的一层包装,因此必须实现:

  • read()
  • write(data)

同时可以通过 self.storage 访问底层 storage。

在开始实现之前,先看一下 middleware 处理的数据结构:

python 复制代码
{
    '_default': {
        1: {'key': 'value'},
        2: {'key': 'value'},
        # other items
    },
    # other tables
}

可以看到数据结构是:

  • 多 table

    • 每个 table 包含多个 document

因此需要两层遍历:

  • 遍历 table
  • 遍历 document

Middleware 实现示例:

python 复制代码
class RemoveEmptyItemsMiddleware(Middleware):
    def __init__(self, storage_cls):
        # Any middleware *has* to call the super constructor
        # with storage_cls
        super().__init__(storage_cls)  # (1)

    def read(self):
        data = self.storage.read()

        for table_name in data:
            table_data = data[table_name]

            for doc_id in table_data:
                item = table_data[doc_id]

                if item == {}:
                    del table_data[doc_id]

        return data

    def write(self, data):
        for table_name in data:
            table_data = data[table_name]

            for doc_id in table_data:
                item = table_data[doc_id]

                if item == {}:
                    del table_data[doc_id]

        self.storage.write(data)

    def close(self):
        self.storage.close()

关键点说明:

(1) 必须调用父类构造函数

python 复制代码
super().__init__(storage_cls)

这一点是必须的,因为 middleware 需要知道底层使用的 storage 类型。

通过 middleware 包装 storage:

python 复制代码
db = TinyDB(storage=RemoveEmptyItemsMiddleware(SomeStorageClass))

默认行为说明:

  • SomeStorageClass:你指定的 storage

  • 如果不传 storage:

    • 默认使用 JSONStorage

middleware 的本质是:

在 storage 之上加一层"数据拦截与修改层"

它不会改变存储方式,而是:

  • 在读数据时加工数据
  • 在写数据时过滤或修改数据

使用 hooks 与覆盖机制

在某些情况下,仅靠自定义 storage 或 middleware 已经无法满足你对 TinyDB 的扩展需求。这时可以使用内置的 hooks 和 override 机制来修改其行为。

例如,可以通过修改默认表名来改变行为:

python 复制代码
TinyDB.default_table_name = 'my_table_name'

TinyDB 的 TinyDBTable 类都支持通过 hooks 和 override 进行行为修改。

例如,你可以通过 TinyDB.table_class 访问 Table 类,并修改其默认行为:

python 复制代码
TinyDB.table_class.default_query_cache_capacity = 100

继承 TinyDB 与 Table

最后一种方式是直接通过继承来修改 TinyDB 的行为,这种方式比前面的扩展机制更底层,可以更深度地控制内部逻辑。

你可以通过创建子类来扩展 Table

python 复制代码
class MyTable(Table):
    # Add your method overrides
    ...

然后将其注册到 TinyDB:

python 复制代码
TinyDB.table_class = MyTable

# Continue using TinyDB as usual

TinyDB 的源码本身就围绕扩展性进行了设计,内部代码和方法都有详细注释,说明了各个机制的工作方式。

如果你需要更深层的自定义行为,可以直接阅读源码并按需修改实现。

结尾(Conclusion)

整体来看,TinyDB 给人的感觉非常一致:简单、直接、Pythonic

它并没有构建一套复杂的数据库引擎,也没有引入传统意义上的查询优化器或执行计划系统。很多"看起来很高级"的能力,本质上都建立在 Python 自身已有的能力之上。

比如缓存机制,本质上就是对函数结果做了一层封装,很多场景可以直接对应到 Python 标准库中的 lru_cache 思路;而 storage(例如 JSONStorage),也只是对 JSON 文件的读写封装,并没有额外的存储引擎抽象。

再看 Query 系统,它看起来像 ORM,甚至有一点 DSL 的味道,但实际上并没有 SQL 引擎那种复杂的解析、优化与执行流程。它更像是:

一套基于 Python 表达式 + filter/map 的管道式过滤机制

本质上就是对 list / dict 数据做条件筛选,只是用了一种更优雅的表达方式包装起来而已。很多能力在逻辑上甚至等价于我们日常写的列表推导式或 filter 操作。

从代码规模来看,整个项目大约 2000 行左右(源码地址:https://github.com/msiemens/tinydb/tree/master),这也进一步说明了它的定位:不是一个完整数据库系统,而是一个轻量级的嵌入式数据操作工具

如果换一个角度来看,我更倾向于把 TinyDB 理解为:

一个"带持久化能力的 dict + 查询过滤层"的组合体

它确实像数据库,但更本质的行为是围绕 Python 的字典结构进行增删查改,再叠加了一层查询表达能力。

这也解释了它的性能边界:由于所有查询本质上仍然是 Python 层的遍历与过滤,当数据量增长时,性能自然会线性下降,这一点与 SQLite 这种具备独立存储引擎与查询优化能力的系统完全不同。

虽然 SQLite 和 TinyDB 都常被归类为"轻量级数据库",但它们在架构复杂度上并不在一个层级:

  • SQLite 是完整的数据库引擎(解析器 + 查询优化器 + 存储引擎)
  • TinyDB 更像是 Python 世界里的"数据操作工具层"

总的来说:

TinyDB 的价值不在于"像数据库",而在于"用数据库的方式去组织 Python 数据操作"。

它适合轻量、嵌入式、快速开发的场景,但并不是为了替代传统数据库系统而设计的。

相关推荐
gushinghsjj1 小时前
什么是主数据管理平台?怎么构建主数据管理平台?
大数据·数据库
qq_654366981 小时前
如何排查Oracle客户端连接慢_DNS解析超时与sqlnet配置优化
jvm·数据库·python
黄昏晓x2 小时前
数据库基础
数据库·adb
李白客2 小时前
国产数据库选型指南:从技术路线到实战要点
运维·数据库·数据库架构·迁移学习
Nalu CONG2 小时前
mysql数据被误删的恢复方案
数据库·mysql
小宋加油啊2 小时前
工作中数据库知识
数据库
杨浦老苏2 小时前
数据库备份管理工具DBackup
数据库·docker·备份·群晖
一 乐2 小时前
交通感知与车路协同系统|基于springboot + vue交通感知与车路协同系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·交通感知与车路协同系统
NineData2 小时前
NineData 将亮相 DACon 2026 上海站!解锁 AGI 时代数据“智理”新范式
数据库·架构·agi·ninedata·数据复制·数据迁移工具·dacon2026