mcpField
D
db-defaults

db-defaults

github.com/theodoread数据与数据库037 次安装4 周前更新
代码仓库

关于 db-defaults

db-defaults 面向个人、本地优先的 Python 项目,帮助按用途选择存储方案,避免每次从头比较。它提供各类默认选项、升级路径和代码片段,并强调可在本地 pytest 中测试。

主要功能

  1. 01按数据用途提供存储默认选项
  2. 02说明规模变化时的升级路径
  3. 03为各类别提供 Python 代码片段
  4. 04将本地 pytest 可测试性纳入标准
  5. 05建议不要模拟可实际运行的存储

适用场景

  1. 01为本地 Python 项目选择缓存
  2. 02为项目数据选择关系型存储
  3. 03选择本地全文或向量搜索方案
  4. 04判断存储需求何时超出本地默认方案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/theodoread/agent-skills --skill db-defaults

安装后在 Claude Code 中输入/db-defaults确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
rationale.md
references/snippets/
analytical-olap.py
blob-storage.py
cache.py
cron-scheduler.py
document-store.py
full-text-search.py
graph-data.py
in-process-state.py
job-queue.py
pubsub.py
relational-oltp.py
relational-simple.py
time-series.py
vector-search.py

详细介绍

各用例的默认存储技术

面向个人、以 Python 为先、本地优先的项目。在没有明确要求分析替代方案的情况下,需要开始处理存储/缓存需求时适用——从此表中选择,不要每次会话都从头重新评估。若某个类别确实不适用,或需求超出默认方案的规模,则可以偏离默认方案(参见各类别的“升级到”说明)——这样做的目的是避免新的会话/模型无缘无故地悄悄选用其他方案,而不是禁止酌情判断。

以下每项的选择标准:仅采用 MIT/Apache-2.0/BSD 风格的宽松许可证;广受欢迎且积极维护(通过 GitHub/PyPI 上的实际活动验证,而不是凭感觉);可在普通的 pytest 运行中完全测试——使用内存或限定在 tmp_path 范围内,不需要 Docker/云账户/CI 服务;API 样板代码少,编码 Agent 无需太多仪式或间接操作就能正确使用。选择侧重于为每项需求选用最合适的工具,而非尽量减少项目中的技术种类——不要强行整合下方各类别并未要求整合的方案。

切实利用可测试性标准:不要模拟这些方案。 选择一个测试套件可以启动和停止的存储方案,意义就在于让测试运行真实实例;在这里使用替身会浪费这项选择带来的好处。python-conventions 中的“不要替代测试套件能够实际运行的任何内容”说明了这条规则及其边界。

安全性有意不作为选择因素。 此处的所有默认方案都是为本地、个人规模的使用而选择——本页的任何内容都不应被视为生产环境/多租户/面向互联网的建议。遇到这些情况时,应采用各类别“升级到”所列的方案。

进程内临时状态

  • 示例:references/snippets/in-process-state.py
  • 默认方案:纯标准库(threading.Lock、time.monotonic、字典)——无需库
  • 原因:无需持久化;对于“保存一个数字,并用一个锁保护它”这种需求,使用库只会增加纯粹的开销
  • 升级到:不适用——一旦需要在重启后保留数据,就应改用缓存或关系型类别

缓存(TTL/驱逐)

  • 示例:references/snippets/cache.py
  • 默认方案:diskcache(Apache-2.0,纯 Python)
  • 原因:基于磁盘,可处理 TTL/驱逐,无需自行实现;在 pytest 中使用 tmp_path 夹具非常简单
  • 升级到:Redis——一旦多个进程/机器需要共享同一个缓存

关系型——简单(表少,形似键值)

  • 示例:references/snippets/relational-simple.py
  • 默认方案:标准库 sqlite3、原生 SQL
  • 原因:零依赖;对于如此简单的数据结构,无需 ORM 的繁琐流程;可在 pytest 中使用 :memory: 或 tmp_path
  • 升级到:Postgres

关系型 — 复杂 / OLTP(许多表、真正的联接、迁移、频繁写入)

  • 示例:references/snippets/relational-oltp.py
  • 默认方案:sqlalchemy + alembic(均为 MIT 许可证)
  • 原因:确实需要 Engine/Session/声明式模型这一套流程,但它带来了迁移和关系安全保障;对于真正需要事务的多表结构,其他更轻量的方案都无法提供这些保障。
  • 通过声明式基类的 type_annotation_map 为普通类型定义列类型词汇表,并用 Annotated 别名指定需要逐列参数的类型。 这样,每个模型中的每个 Mapped[Decimal] 都会按相同方式解析,不会有人因为凭记忆在另一个文件中编写而绕过这一规则。这也诚实地回答了“为什么用 ORM 而不是 Core”——Core 也能生成相同的 SQL,只不过需要在每一列上重复写出类型词汇表。
  • 升级到:Postgres
  • SQLite 层会悄无声息地破坏 Decimal 精度并丢弃 tzinfo,而升级到的 Postgres 不会出现这两种问题——这一点在此尤其重要,因为“可以完全在普通的 pytest 运行中测试”正是每个采用此方案的项目都使用 SQLite 的原因。以 SQLAlchemy 2.0.52 为准:
    • Numeric 会通过浮点数往返转换 Decimal,且不会发出警告:1234567890123456789.000000001 会变成 1234567890123456768.0000000000。问题出在库本身——engine/default.py 将 supports_native_decimal 设为 False,而 SQLite 方言并未覆盖它,因此 sqltypes.py 会执行注释为“DBAPI returns floats, convert”的分支,并量化到 10 位小数。解决方法是使用存储精确字符串的 TypeDecorator。
    • 带时区的 datetime 取出后会变成不带时区的值。这不是 SQLite 的限制:dialects/sqlite/base.py 中的 DATETIME.bind_processor 根本不会读取 tzinfo。
    • 陷阱在于它们很容易被掩盖。首次测试 Decimal 时,使用 10 位数字的值会_通过_,因为该值可以在浮点数中保持精确——于是,使用真实金额作为测试数据的项目会得出 Numeric 没问题的结论,然后上线。请使用足够长、能触发错误的值进行测试。
  • 在 __table_args__ 中传入 sqlite_strict=True,让测试方言不再比它所模拟的 Postgres 更宽松,避免测试专用方言带来的失效模式。这不是可以随意添加的额外关键字参数,示例展示了完整做法:STRICT 只接受 INT/INTEGER/REAL/TEXT/BLOB/ANY,因此 create_all 遇到 NUMERIC(38, 10)、DATETIME,以及普通 String(64) 所生成的 VARCHAR(64) 时都会直接失败(这三项均在 2.0.52 上实测)。String(64).with_variant(Text(), "sqlite") 可在 Postgres 上保留真实的长度限制,而上述 Decimal/datetime TypeDecorator 则可使另外两列满足要求。因此,采用 STRICT 并排除这两个陷阱是同一项工作;这也正是应该一次性都做,而不是都不做的理由。
  • Alembic 的 SQLite 批量模式会埋下数据完整性隐患。 在 SQLite 上,batch_alter_table 通过重建表来实现变更,这会删除该表的触发器;而 Alembic 对触发器完全没有感知,搜索整个包中的“trigger”只会找到一条无关注释。此外,根据 batch.py 自身的 TODO,它也会跳过既被反射又未命名的 CheckConstraint。有两种缓解方式,任选其一都可以:为每个约束命名,或者决定 SQLite 层永不执行迁移,而始终根据当前元数据创建。

分析型 / OLAP(针对结构化数据的读取 / 聚合密集型查询)

  • 代码片段:references/snippets/analytical-olap.py
  • 默认选择:duckdb(MIT — 知识产权由一家非营利基金会持有,专门用于确保其「永久」采用 MIT 许可)
  • 原因:duckdb.connect(":memory:") 可通过普通连接直接使用原生 SQL,无需 ORM 繁琐配置——对于这种使用场景,样板代码远少于 SQLAlchemy,而且连接和聚合的速度确实很快。仅支持单写入者(适合个人规模的本地使用);多写入者「Quack」协议于 2026 年 5 月发布,但仍是新技术
  • 升级选择:Postgres+DuckDB 扩展,或 MotherDuck

文档存储(无模式 / 半结构化 JSON)

  • 代码片段:references/snippets/document-store.py
  • 默认选择:tinydb(MIT,纯 Python)
  • 原因:提供真正类似 dict 的 API(db.insert({...})、db.search(Query().field == x))——完全不用 SQL;使用 MemoryStorage() 或 tmp_path 进行 pytest 测试。维护者称其处于「维护模式」(功能完备且稳定)——仍会发布错误修复版本,并未被弃用
  • 替代方案:sqlitedict(Apache-2.0),适用于纯粹的键→对象存储且无需字段查询的情况——使用前请注意其数年来没有提交记录
  • 升级选择:MongoDB

全文搜索

  • 代码片段:references/snippets/full-text-search.py
  • 默认选择:SQLite FTS5 虚拟表——随标准库 sqlite3 一同提供,无需新增依赖,内置 bm25() 排序
  • 替代方案:bm25s(MIT),适用于与数据存储解耦的独立 RAG 风格评分器;tantivy(MIT,为 Rust tantivy 搜索引擎提供 Python 绑定——使用 pip install tantivy,而非 tantivy-py,后者是另一个过时的软件包),适用于需要比 FTS5 更丰富的功能集时进行更重型的类 Lucene 搜索
  • 避免使用:Whoosh——长期无人维护
  • 升级选择:Elasticsearch 或 Meilisearch(两者都需要运行服务,因此不作为本地默认选择)

向量 / 嵌入相似性搜索

  • 代码片段:references/snippets/vector-search.py
  • 默认选择:qdrant-client(Apache-2.0)
  • 原因:QdrantClient(":memory:") 或 QdrantClient(path=tmp_path) 完全无需服务;之后升级时,只需更换构造函数参数(url=.../云凭据)——类相同、方法相同,且已根据 Qdrant 官方文档验证。通过一个 API 处理向量、元数据和持久化
  • 替代方案:chromadb(Apache-2.0)——社区规模 / 影响力大得多,初次上手的 API 可以说更简单(add()/query());如果上手方便比经过验证的无须重写升级路径更重要,这是合理的选择
  • 升级选择:Qdrant Cloud(客户端代码无需更改)或 Pinecone/Weaviate

后台任务 / 消息队列

  • 代码片段:references/snippets/job-queue.py
  • 默认选择:huey(MIT)
  • 原因:SqliteHuey(..., immediate=True) 会在进程内同步运行 @huey.task() 函数——无需消费者子进程,也无需编排,普通的 pytest 测试只需直接调用函数即可。基于装饰器的 API 简洁,拥有 15 年的使用记录,待处理的未解决问题几乎为零
  • 升级选择:Celery/RQ/Dramatiq——都需要实际的消息代理(Redis/RabbitMQ);当你需要多个工作进程 / 多台机器时,这正是选择它们的原因

Cron/定时重复任务

  • 代码片段:references/snippets/cron-scheduler.py
  • 默认选择:apscheduler 3.x(MIT)
  • 原因:指向 sqlite:/// URL 的 SQLAlchemyJobStore 可以在重启后保留计划任务,不需要外部代理;即使完全不需要调度,也可以单纯用作队列。需要了解真实的单点维护风险,但这并不是完全避开它的理由:一位占主导地位的维护者(1,134 次提交,而贡献量第二高的贡献者只有 6 次),约 2 年前提交且未经审查的 PR,以及从 2020 年起就一直处于 alpha 阶段、至今没有稳定版的 v4——3.x 仍是唯一实用的选择,并且仍在积极修补(最新发布版本截至 2026 年 6 月)
  • 替代方案:schedule(MIT)——零依赖,进程内 API 简单易用,但不支持持久化(重启会丢失所有计划任务),而且自 2024 年 5 月以来就没有更新;适用于纯进程内场景的轻量备用方案,不是受治理因素驱动的替代品
  • 升级选择:当重复任务需要在一台以上的机器上持续运行时,改用托管调度器(例如云端 cron)

发布/订阅/事件流

  • 代码片段:references/snippets/pubsub.py
  • 默认选择:blinker(MIT)——进程内信号/观察者分发(signal.connect(receiver)、signal.send(sender))
  • 原因:Flask 自身也用这个库处理信号;无需繁琐配置,可在进程内向多个订阅者分发,无需服务端,并且极易测试
  • 升级选择:NATS(Apache-2.0)——它是真正不同的工具,而非这个工具的更大版本:需要单独配置 nats-server 进程,而且没有官方维护的 Python pytest fixture 包可用于本地测试(两个社区尝试都已停止维护),因此即使它在实际运行服务端后是个出色的产品,也不符合这里的本地默认选择条件

图数据

  • 代码片段:references/snippets/graph-data.py
  • 默认选择:ladybug(MIT,pip install ladybug)——嵌入式,使用 Cypher 查询语言,无需服务端
  • 原因:这是 Kuzu 的活跃维护延续版本,由社区在 Kuzu 于 2025 年 10 月归档(Apple 收购)后 3 天分叉而来——底层引擎相同,维护团队换新,并非重写。每天都有提交,有 MIT 许可证,并明确列出了活跃维护者。作为项目名称还不到一年,因此应视为前景不错但尚年轻,而非像 SQLite 那样经过实战检验
  • 轻量替代方案:如果关系建模不需要真正的图遍历性能或查询语言,那么在关系型存储中使用普通的 edges(src, dst, relation) 表仍然更简单——只有在需要以超出玩具规模的实际图算法/遍历时,才选择 ladybug。两种方案都可以配合 networkx(BSD-3-Clause),对已加载到内存中的数据运行图算法
  • 升级选择:Neo4j Aura、Amazon Neptune

Blob/对象存储

  • 代码片段:references/snippets/blob-storage.py
  • 默认选择:直接使用 pathlib.Path 写入文件——无需库
  • 原因:零依赖,使用 tmp_path 测试非常简单;尚无具体计划就为云存储添加抽象,正是 YAGNI 原则所针对的情况
  • 升级选择:当云存储成为实际计划而非可能选项时,使用 fsspec(BSD-3-Clause)+ s3fs/gcsfs——调用方式仍为 fs.open()/fs.ls(),只需更改协议字符串

时间序列数据

  • 代码片段:references/snippets/time-series.py
  • 默认选择:duckdb(MIT)——与上方分析/OLAP 类别使用同一工具
  • 原因:DuckDB 的窗口函数、time_bucket、缺口填补和 ASOF 联接在时间序列分析方面确实很出色;与用原始 SQL 手动编写相同查询相比,能显著减少样板代码——对于这一特定需求,它是更好的工具,无论项目是否也需要它来处理 OLAP 工作
  • 轻量替代方案:使用带时间戳索引列的普通 SQLite 表,前提是需求确实只是“存储带时间戳的行并按范围筛选”,不涉及重采样或窗口计算
  • 升级选择:InfluxDB、TimescaleDB

编辑此技能

安装时,此文件会被_复制_到 ~/.agents/skills/db-defaults,而不是创建符号链接,因此编辑已部署的副本会造成本地偏差——这正是此技能要防止的情况,而且修改不会影响其他机器。请在安装此技能的源代码仓库中编辑,然后推送,并重新运行 skills add <that source> --global --skill db-defaults,以刷新每个项目的副本。如果你安装的是其他人的仓库,而不是自己的分叉仓库,那么源代码属于对方:请在那里提交 issue 或拉取请求。

入门代码片段

references/snippets/ 中为上述每个类别提供了一个真实、通过 ruff 检查且独立完整的 Python 文件——每个文件的文档字符串中都包含经过验证的 pip install 命令,并提供一个可运行的 test_* 函数来展示 pytest 本地测试模式;这些代码可以直接复制粘贴,无需像教程那样修改。上方每个类别的“代码片段:”行都直接链接到对应文件,因此无需打开其他文件。这里还记录了编写时发现的两个真实命名陷阱,并在相关位置加以说明:full-text-search.py(tantivy 与 tantivy-py 的 PyPI 混淆)和 cron-scheduler.py(不存在的 SQLiteJobStore 类名)。

完整说明

请参阅 references/rationale.md——其中包含 GitHub/PyPI 证据、每个类别中经过考虑后予以排除的选项,以及上方每个分支/替代方案背后的理由。

评论

支持 Markdown