- 数据库
- 嵌入式数据库
【免费下载链接】tinydb
TinyDB is a lightweight document oriented database optimized for your happiness :)
TinyDB 是一个轻量级、面向文档的 NoSQL 数据库,核心设计目标就是"为了你的幸福感而优化"(optimized for your happiness)。为了让这份幸福感可延续,官方维护了一份社区扩展清单(见 docs/extensions.rst),收录了从性能加速、异步兼容到序列化、索引、事务等方向的第三方扩展。本文以这份扩展清单为骨架,逐一解读每个扩展的定位与适用场景,并结合仓库源码(tinydb/storages.py、tinydb/middlewares.py、tinydb/database.py)说明这些扩展究竟"扩展了什么",最后给出自己动手编写 Storage 与 Middleware 的完整实战方案。读完后,你将能根据业务需求快速挑选合适的 TinyDB 扩展,也能独立为 TinyDB 定制存储与中间件。
一、为什么 TinyDB 需要扩展
TinyDB 的核心哲学是"小而美":它把数据持久化抽象为Storage(存储层),把读写行为装饰抽象为Middleware(中间件),把查询缓存、文档 ID 等行为封装在Table与TinyDB类中。官方默认只提供两种存储(JSON 文件存储与内存存储),因此当业务需要更高的性能、异步 IO、复杂对象序列化或原子事务时,社区扩展就派上了用场。
从源码看,TinyDB.__init__会通过storage = kwargs.pop('storage', self.default_storage_class)取出用户传入的存储类,并把除storage外的所有参数原样转发给存储构造函数(tinydb/database.py)。这一设计意味着:任何符合Storage接口的类——无论是社区扩展还是你自己写的——都可以无缝替换默认的JSONStorage,这正是整个扩展生态得以运转的基石。
二、官方收录的社区扩展全览
官方文档共收录了 11 个扩展,按状态可分为三类:stable(稳定可用)、beta(测试阶段)与inactive(不再维护)。下面逐一介绍。
2.1 tinydb-rust(beta):Rust 重写的性能加速器
- 状态:beta
- 定位:使用 Rust 重新实现 TinyDB 的"即插即用"替代品(drop-in reimplementation),目标是获得更好的性能。
- 适用场景:对读写吞吐有较高要求、数据量较大、且不想改变 TinyDB 调用习惯的项目。由于是 beta 状态,生产环境引入前应充分压测并关注其与当前 TinyDB 版本的 API 对齐情况。
2.2 aiotinydb(stable):asyncio 兼容层
- 状态:stable
- 定位:为 TinyDB 提供 asyncio 兼容垫片(shim),让 TinyDB 能在 asyncio 感知的上下文中使用,避免同步 IO 阻塞事件循环。
- 适用场景:FastAPI、aiohttp 等异步框架项目中需要本地 JSON 数据库,又不希望用
run_in_executor手工包裹每一个数据库调用。它把阻塞 IO 隔离在异步接口之后,保持 TinyDB 的 API 语义不变。
2.3 BetterJSONStorage(stable):Orjson + BLOSC 加速
- 状态:stable
- 定位:为 TinyDB 提供更快的"存储类型":使用更快的 Orjson 库解析 JSON,并用 BLOSC 进行压缩。
- 适用场景:JSON 文件体积大、读写频繁的项目。它本质上是
Storage接口的一个高性能实现——序列化与反序列化是 TinyDB 每次读写磁盘的必经环节(见 tinydb/storages.py 中JSONStorage.write对json.dumps的调用),换用更快的序列化库能直接降低这一环节的耗时。
2.4 tinydb-serialization(stable):复杂对象序列化
- 状态:stable
- 定位:为 TinyDB 原本无法处理的 Python 对象提供序列化支持。
- 适用场景:需要把
datetime、自定义类实例等非 JSON 原生类型直接存入数据库。默认的JSONStorage依赖 Python 内置json模块,只能处理基本数据类型;该扩展通过在写前/读后加一层对象转换,让"不能存"变成"可以存"。
2.5 tinydb-smartcache(stable):智能查询缓存
- 状态:stable
- 定位:为 TinyDB 提供智能查询缓存:在插入、删除、更新文档时同步更新缓存,而不是使缓存整体失效。适合"查询频繁、数据变动少"的工作负载。
- 适用场景:默认情况下,TinyDB 的查询缓存会在每次写操作后整体丢弃(
Table文档注释明确说明"writing data, the whole cache is discarded")。当数据变动小而查询量巨大时,这种"全量失效"策略会造成大量重复读取。该扩展精确跟踪哪些查询结果受哪些写操作影响,从而大幅提升命中率。
2.6 tinyrecord(stable):实验性原子事务
- 状态:stable
- 定位:为 TinyDB 实现实验性的原子事务支持。采用"先记录后执行"(record-first then execute)的架构,最小化线程锁内停留的时间。
- 适用场景:多线程环境中需要保证一批操作要么全部成功要么全部失败。它把待执行操作先记录成事务日志,再在锁内快速回放,从而缩短锁持有时间、降低并发冲突概率。
2.7 tinydb-appengine(inactive):Google App Engine 存储
- 状态:inactive
- 定位:为 TinyDB 提供 App Engine 存储,支持只读 JSON 使用方式。
- 适用场景:曾部署在 Google App Engine 上的旧项目。该扩展已停止维护,新项目应避免选用,可作为参考实现了解 Storage 如何对接平台特定存储。
2.8 TinyDBTimestamps(inactive):自动时间戳
- 状态:inactive
- 定位:自动为 TinyDB 文档添加创建时间(create at)与更新时间(update at)戳。
- 适用场景:需要审计或按时间排序的项目。功能类似一些 ORM 的
created_at/updated_at自动填充。同样已停止维护,如需要此能力可自行实现(参考下文"自定义操作"的思路)。
2.9 tinyindex(inactive):文档索引
- 状态:inactive
- 定位:为 TinyDB 提供文档索引,保证在表未被修改的前提下文档以确定性顺序产出。
- 适用场景:依赖稳定迭代顺序的读多写少场景。已停止维护,可作为理解"确定性遍历"需求的参考。
2.10 tinymongo(inactive):MongoDB 平替封装
- 状态:inactive
- 定位:一个简单的封装,允许把 TinyDB 作为 MongoDB 的扁平文件即插即用替代品。
- 适用场景:希望保留 MongoDB 风格 API、但不想部署服务端、只需单机扁平文件存储的项目。已停止维护。
2.11 TinyMP(inactive):MessagePack 存储
- 状态:inactive
- 定位:基于 MessagePack 的 TinyDB 存储扩展。
- 适用场景:希望以二进制格式存储、追求比 JSON 更紧凑体积的场景。已停止维护。
2.12 扩展选型速查
| 扩展 | 状态 | 解决的问题 |
|---|---|---|
| tinydb-rust | beta | 整体性能(Rust 重写) |
| aiotinydb | stable | asyncio 异步兼容 |
| BetterJSONStorage | stable | JSON 解析/压缩性能 |
| tinydb-serialization | stable | 复杂对象序列化 |
| tinydb-smartcache | stable | 查询缓存命中率 |
| tinyrecord | stable | 原子事务 |
| tinydb-appengine | inactive | App Engine 存储 |
| TinyDBTimestamps | inactive | 自动时间戳 |
| tinyindex | inactive | 确定性文档索引 |
| tinymongo | inactive | MongoDB 风格 API |
| TinyMP | inactive | MessagePack 存储 |
需要强调的是,inactive 状态的扩展不代表功能失效,只是说明维护者已停止跟进上游 TinyDB 的版本演进,选用前务必验证其与当前 TinyDB 版本的兼容性。
三、扩展背后的统一接口:Storage 与 Middleware
要真正理解这些扩展,必须先看懂 TinyDB 的两层抽象。它们定义在 tinydb/storages.py 与 tinydb/middlewares.py 中。
3.1 Storage:一切持久化的入口
Storage是抽象基类(ABC),核心契约只有两个抽象方法(tinydb/storages.py):
read() -> Optional[dict[str, dict[str, Any]]]:读取当前数据库状态,负责一切反序列化;返回None表示存储为空(TinyDB 借此完成初始化)。write(data):把数据库当前状态写入存储,负责一切序列化。close()(可选):关闭文件句柄等资源,默认空实现。
默认实现JSONStorage的构造函数签名值得细看(tinydb/storages.py):
def __init__(self, path, create_dirs=False, encoding=None, access_mode='r+', **kwargs):path:JSON 文件路径;create_dirs:为True时自动创建缺失的父目录;encoding:文件编码,测试中可见用cp936写入日文再按错误编码读取会抛JSONDecodeError(tests/test_storages.py);access_mode:文件打开模式,仅r、rb、r+、rb+是安全选择,使用w等写模式会触发数据丢失警告(tests/test_storages.py 用pytest.warns验证了该行为);**kwargs:原样转发给json.dumps,可用于美化输出,如TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': ')),测试 tests/test_storages.py 精确断言了格式化后的文件内容。
write实现中还有三个容易被忽略的细节:写前seek(0)定位到文件头、写后flush()+os.fsync()强制落盘、最后truncate()清掉旧文件尾部的残留数据(tinydb/storages.py)——这些细节保证了"文件变短"场景下的数据正确性。
3.2 Middleware:透明的行为装饰器
Middleware是一个可调用对象包装器(tinydb/middlewares.py)。关键机制有三点:
- 构造时接收的是存储类而非实例:
Middleware.__init__(self, storage_cls)保存类,__call__时才用 TinyDB 转发来的参数实例化真正的存储(self.storage = self._storage_cls(*args, **kwargs))。 __getattr__透明转发:中间件未定义的属性访问会转发给底层self.storage,因此它几乎"看起来就是"一个存储。- 可嵌套:
FirstMiddleware(SecondMiddleware(JSONStorage))会形成链式调用,__call__逐层初始化。
官方内置的CachingMiddleware是理解中间件价值的最佳样例(tinydb/middlewares.py):
WRITE_CACHE_SIZE = 1000:累计写操作达到该阈值才真正写盘一次;- 读操作永远走缓存(
cache为空时才回源读); flush()强制把缓存写入底层存储;close()先flush()再关闭底层存储;- 关闭后继续读写会抛
ValueError('I/O operation on closed storage')。
测试 tests/test_middlewares.py 完整验证了这些行为:写入 2 次后底层存储仍为空、第 3 次写入触发落盘(WRITE_CACHE_SIZE = 3时);flush()与close()都能手动触发落盘;关闭后读写均抛异常。这些测试文件本身就是理解扩展行为的"活文档"。
四、实战:自己动手写一个扩展
理解了 Storage 与 Middleware 接口后,你就可以按官方扩展同等的思路自定义扩展。以下方案源自官方扩展指南 docs/extend.rst,与本仓库源码逐行对应。
4.1 自定义 Storage:以 YAML 存储为例
实现一个存储只需三个方法。下面的YAMLStorage是官方文档中的完整示例(需要pip install pyyaml):
import yaml from tinydb.storages import Storage class YAMLStorage(Storage): def __init__(self, filename): # (1) 接收 TinyDB 转发来的全部参数 self.filename = filename def read(self): with open(self.filename) as handle: try: data = yaml.safe_load(handle.read()) # (2) 使用 safe_load 处理潜在不可信数据 return data except yaml.YAMLError: return None # (3) 存储未初始化时返回 None def write(self, data): with open(self.filename, 'w+') as handle: yaml.dump(data, handle) def close(self): # (4) 需要清理资源时实现 pass四个关键点:
- 构造函数参数来源:
TinyDB('db.yml', storage=YAMLStorage)会把'db.yml'作为位置参数传给YAMLStorage.__init__(storage参数被TinyDB自己消费,见 tinydb/database.py)。若你的构造函数接受可调用对象,切勿从不可信或用户可控的输入推导它们。 - 反序列化安全:处理潜在不可信来源的数据时使用
yaml.safe_load而非yaml.load,避免任意代码执行风险。 - 空存储语义:未初始化时返回
None,TinyDB 会据此完成内部初始化(与 tinydb/storages.py 中JSONStorage.read对空文件的处理一致)。 - 资源清理:需要关闭文件句柄时实现
close(),并通过db.close()或上下文管理器触发:
with TinyDB('db.yml', storage=YAMLStorage) as db: # ...仓库测试 tests/test_storages.py 中有一个更完整的 YAML 存储实现,它还额外处理了Document子类的 YAML 表示,可作为参考。注意:Storage是抽象基类,只实现read/write其中之一是无法实例化的(测试 tests/test_storages.py 验证了这一点)。
4.2 自定义 Middleware:过滤空文档
中间件包裹在存储外层,可以拦截并改写读写的数据流。数据在中间件中的形态是"表名 -> 文档 ID -> 文档"的两层嵌套结构:
{ '_default': { 1: {'key': 'value'}, 2: {'key': 'value'}, }, # 其他表... }据此实现一个"移除空文档"的中间件:
from tinydb.middlewares import Middleware class RemoveEmptyItemsMiddleware(Middleware): def __init__(self, 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: if table_data[doc_id] == {}: 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: if table_data[doc_id] == {}: del table_data[doc_id] self.storage.write(data) def close(self): self.storage.close()使用时把中间件实例传给storage参数,其中SomeStorageClass可替换为任意存储类(省略则默认使用JSONStorage):
db = TinyDB(storage=RemoveEmptyItemsMiddleware(SomeStorageClass))4.3 使用 hooks 与类变量覆盖默认行为
如果既不想写存储也不想写中间件,TinyDB 还提供了轻量的"钩子与覆盖点"(hooks and overrides):
- 修改默认表名:
TinyDB.default_table_name = 'my_table_name'(对单个实例也可db.default_table_name = ...); - 修改查询缓存容量:
TinyDB.table_class.default_query_cache_capacity = 100; - 修改全局默认存储:
TinyDB.default_storage_class = MemoryStorage。
这些类变量都定义在 tinydb/database.py 与 tinydb/table.py 中,包括table_class、default_table_name、default_storage_class(TinyDB 侧),以及document_class、document_id_class、query_cache_class、default_query_cache_capacity(Table 侧)。
4.4 更进一步:子类化 TinyDB 与 Table
当钩子不够用时,可以子类化并替换 TinyDB 内部使用的类:
from tinydb import TinyDB from tinydb.table import Table class MyTable(Table): # 添加你的方法覆盖 ... TinyDB.table_class = MyTable # 继续像往常一样使用 TinyDB官方在 docs/extend.rst 中强调,TinyDB 的源码本身就是面向扩展编写的,内部方法也带有说明性文档,深入阅读源码(尤其 tinydb/table.py 与 tinydb/database.py)可以找到一切可覆盖的扩展点。
五、选用与使用扩展时的注意事项
结合官方文档 docs/extensions.rst、docs/usage.rst 与源码,以下几点值得特别留意:
- 安全边界:
JSONStorage会把**kwargs转发给json.dumps,其中default、cls等可调用参数会在每次写操作时被进程内执行(tinydb/storages.py 的文档注释明确警告)。绝不要把不可信或用户可控的值传给这类参数;同理,查询 API 中的可调用谓词(lambda、Query().test、Query().map)也是进程内执行的,不能来自不可信输入。 - 查询缓存的正确认知:默认查询缓存(
Table内部使用LRUCache,见 tinydb/table.py)只缓存本进程内的查询结果,不感知外部进程对底层文件的修改;若外部修改了数据,需用db.clear_cache()强制重新读取。重复查询返回的是缓存中同一批文档对象,返回列表虽是新副本,但文档本身与缓存共享——请按只读对待。cache_size=0可禁用缓存,cache_size=None则不限容量(不限容量时配合test()的 lambda 谓词可能造成内存泄漏)。 - 多实例风险:同一数据文件上打开多个 TinyDB 实例可能因查询缓存导致意外行为,官方文档明确提示此风险。
- 版本兼容:扩展清单中的 beta 与 inactive 状态意味着它们与上游 TinyDB 的演进节奏不一定同步,选型时优先考虑 stable 且活跃维护的扩展(如 aiotinydb、BetterJSONStorage、tinydb-serialization、tinydb-smartcache、tinyrecord)。
- 替代思路:对于 TinyDBTimestamps(时间戳)、tinyindex(确定性索引)这类 inactive 扩展,完全可以通过自定义 Middleware、自定义操作(如 tinydb/operations.py 中的
set、increment、delete等)或子类化 Table 在项目内自行实现,这比依赖无人维护的第三方包更可控。
六、延伸阅读
- 扩展清单原文:docs/extensions.rst
- 扩展机制详解(自定义 Storage/Middleware/钩子/子类化):docs/extend.rst
- 存储与中间件的进阶用法(缓存、格式化 JSON、嵌套中间件):docs/usage.rst 的 "Storage & Middleware" 一节
- 核心实现:tinydb/storages.py、tinydb/middlewares.py、tinydb/database.py、tinydb/table.py
- 行为验证:tests/test_storages.py、tests/test_middlewares.py
无论你是想通过社区扩展获得 Rust 级别的性能、asyncio 兼容或原子事务,还是想亲手写一个贴合业务的存储与中间件,TinyDB 的扩展体系都提供了清晰、统一、可组合的路径——这正是这个"为幸福感而生"的微型数据库最具活力的部分。
- 数据库
- 嵌入式数据库
【免费下载链接】tinydb
TinyDB is a lightweight document oriented database optimized for your happiness :)
相关推荐
tiptap插件生态系统:社区扩展与自定义开发
tiptap插件生态系统:社区扩展与自定义开发 Tiptap作为一款无头编辑器框架(Headless Editor Framework),其核心优势在于通过插件
前端富文本UI组件插件系统抖音去水印批量下载终极指南:三步搞定高清无水印作品保存
抖音去水印批量下载终极指南:三步搞定高清无水印作品保存 你是否曾遇到过这样的场景:看到一个精彩的抖音视频,想要保存下来作为创作素材,却发现下载的视频总是带着烦人
网页爬虫CLIFSCalendar扩展生态:5个必备社区插件与自定义工具指南
FSCalendar扩展生态:5个必备社区插件与自定义工具指南 FSCalendar是一个功能强大的iOS日历控件,通过其丰富的扩展生态系统,开发者可以轻松实现
UI组件移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考