2026-05-21 学习记录 🎯

SQLAlchemy Core 硬啃官方文档、SQLModel 增删改查一对多多对多全过了一遍、CQRS 极简电商订单系统骨架搭建、分布式 ID 雪花算法、以及 FastAPI 作者原来是搞音乐的。

—— 塑梦


SQLAlchemy Core(官方文档硬啃)

第一遍完全看不懂,硬着头皮啃完发现其实就是用 Python 对象描述数据库:

  • MetaData:装表的箱子
  • Table:数据库表在 Python 里的替身
  • Column:列
  • ORM 模型(class User(Base))底层自动生成这些东西

Core vs ORM:

Core ORM
SQL 表达式语言 操作 Python 对象
engine.connect() 直接说话 Session 雇秘书帮你管对象
engine.begin() 自动事务
engine.connect() 需手动 commit

第一次看官方文档看不懂太™正常了。

SQLModel 深入学习

基础 CRUD

  • table=True = 数据库表,不写 = 纯 Pydantic 校验壳
  • Field() 把数据库参数和 Pydantic 校验全揉在一起
  • autoincrement SQLModel 不认识,得塞进 sa_column_kwargs 或直接删掉——整数主键默认自增

一对多

  • ForeignKey 写在多方,Relationship 两边都要写
  • back_populates 的值是对方类里那个属性的名字,两边必须对得上
  • 核心作用:双向绑定——article.author = useruser.articles 自动出现此文章。不写的话各管各的

多对多

  • 需要第三张中间表,用 link_model 指定
  • 两个外键组成联合主键天然防重复
  • 但如果中间表要加数量字段,联合主键就不适用了——因为同一个商品不能在同一个订单里出现多次

逻辑外键

  • 企业开发规范里禁用数据库物理外键
  • ORM 层面仍然可以定义 Relationship,通过 foreign_keys 参数告诉 ORM 用哪列做关联
  • JOIN 查询照样用,数据完整性靠应用代码

多数据库

  • 不同表指定不同 metadata,分别 create_all 到不同数据库
  • 代价是跨库不能 JOIN,需应用层手动多次查询拼接

Pyright 报错踩坑

  1. Decimalwhere() 里比较报类型错误——Pyright 类型推断跟不上 SQLAlchemy 的动态特性,代码能跑
  2. scalars()session.exec() 之后的结果对象的方法,不是 select() 的方法——这个是真写错了

CQRS 练习:极简电商订单系统

架构

1
2
3
4
5
6
7
8
9
cqrs_order/
├── command/
│ ├── handler.py # add_item, remove_item
│ └── model.py # Order, Item, OrderItemLink
├── event.py # EventBus, BaseEvent, OrderAddedItemEvent, OrderRemovedItemEvent
├── query/
│ ├── handler.py # CRUD on OrderReadModel + projection logic
│ └── model.py # OrderReadModel(反范式化宽表)
└── main.py # (待填)

亮点

  • EventBus + @BaseEvent.register 装饰器注册投影,声明式且优雅
  • command / event / projection / query 四层分离,符合 CQRS 标准姿势
  • Order.add_item() 返回 Self 支持链式调用
  • Session 上下文管理器统一使用

踩坑

  1. 循环导入model.py 顶部导入 handler + handler.py 顶部导入 model → Python 加载死锁。解决:把 import 移到方法内部(惰性导入)
  2. ClassVar 用错ClassVar[int] 告诉 SQLModel”这不是数据库列”,外键建不出来
  3. Order(id=order_id):这是新建对象不是查数据库→ session.get(Order, order_id)
  4. 投影算术 bug_remove_itemtotal_price = get_total_price(order_id) + item.price 写成了加号(感谢虚幻捉虫)
  5. DDD 实体方法 vs Python 导入限制:从 DDD 角度看实体有自己的行为完全正确,但 Python 的导入机制不支持双向导入,需要惰性导入来绕开

分布式系统知识

  • 自增主键问题:分布式下有尾部热点——所有插入往 B+ 树最后一页挤
  • UUID 问题:非单调递增引起 B+ 树页分裂和重排,占 128bit
  • 雪花算法:最常用分布式 ID 方案,支持最多 1024 台机器,每秒百万级 ID
    • 时钟回拨问题:NTP 可缓解
  • 生产环境数据库别用 Docker:有状态,扩缩容需复杂同步,资源隔离不如虚拟机
  • 事件风暴:UX/UI → 指令 → 实体 → 规则 → 事件,从事件倒推建模

其他

  • Diagrams:Python 代码画云架构图,自带 AWS/GCP/Azure 官方图标,比 Mermaid 更适合 PPT 汇报
  • FastAPI 作者 Sebastián Ramírez:一个人写了 FastAPI + Typer + SQLModel,还深度参与了 Pydantic V2 的 Rust 核心——他原来是搞音乐的,这跨界离谱程度 🫠
  • sqlite:///:memory::连接不会被 SQLAlchemy 自动关闭(StaticPool),数据在整个程序运行期间都在
  • LeetCode 探险模式数据结构与算法:草率刷完(最后几道难的直接 CV 🤪)