在真实的 PostgreSQL 上实测 Model 与 Table 的映射:类名到表名的转换、没有主键时定义类就报错、各 Column 类型生成的 DDL 与 PG 里实际的列和索引、default 与 server_default 只在 ORM 与绕过 ORM 时的差别、约束违反的异常类型、Mapped[] 注解决定可空、db.reflect() 映射已有的表。
实测 SQLAlchemy 关系的加载策略:默认 lazy=select 访问时才发 SQL 并缓存;取 10 个作者遍历 50 本书,select 11 条、joined 1 条、selectin 2 条;options() 覆盖;raiseload 与 DetachedInstanceError;lazy=dynamic 返回可过滤的查询。
在真实的 PostgreSQL 上对比 joinedload 与 selectinload:发出的 SQL(limit 时包子查询、IN 列表)、一对多必须 unique()、300 个作者 3000 本书的耗时(47.7 / 21.7 / 10.5 ms)、多层嵌套时 JOIN 行数相乘、多对一的差别、IN 列表每块 500 个主键。
实测 N+1 查询:列 100 个作者的页面 101 条 SQL 对 2 条、藏在模板里的 N+1、RECORD_QUERIES 找出发 SQL 的代码行、raiseload 在开发期报错、加了单向 5 ms 延迟的代理后 1482 ms 对 70.8 ms、测试里断言查询数。
实测 SQLAlchemy 的身份映射:同一个 session 里同一行永远是同一个对象、session.get 命中时 0 条 SQL、弱引用与被修改对象的强引用、重新查询看到旧数据(stale=0)以及 populate_existing 与 refresh 与 expire、重复主键、Flask 里请求之间不共享。
在真实的 PostgreSQL 上实测 flush 与 commit:flush 发 SQL 但别的连接看不到、commit 才可见、autoflush 与 no_autoflush、INSERT ... RETURNING 带回 server_default、按外键依赖排序与批量插入、1000 个对象逐个 flush 151 ms 与逐个 commit 265 ms 对一次 commit 18.6 ms。
在真实的 PostgreSQL 上实测 rollback 的实际行为:改过的对象变 expired、新建并 flush 的对象变回 transient、删除的对象恢复、序列号不回退(id 4 之后是 5)、保存点只回到保存点、commit 失败后 PendingRollbackError 与被丢弃的对象、Core 连接的 InFailedSqlTransaction、请求结束时的清理。
在真实的 PostgreSQL 上实测 SQLAlchemy 连接池:默认 QueuePool 5 + 10、惰性创建、耗尽时 1011 ms 后 TimeoutError、溢出连接的关闭时机、服务端杀掉连接后 pre_ping 的作用、池化 0.17 ms 对不池化 1.82 ms、多 worker 下 PG 连接数是各池之和(15 个)。
在真实的 PostgreSQL 与 gunicorn 上实测连接什么时候借出、什么时候归还:autobegin 借出、commit 或 close 归还;不关闭的 connect() 泄漏后第 3 个请求起 500;慢操作前不 commit 时 3 个并发请求总耗时 2195 ms,commit 后 1182 ms;流式响应里查询会一直占着连接。
在真实的 PostgreSQL 上实测 expire_on_commit:默认 commit 之后读属性多发 1 条 SELECT、请求结束后读属性抛 DetachedInstanceError、关掉后读到旧值(触发器改的 ver 对象里是 0 而库里是 1)、commit 1000 个对象后逐个读属性 1000 条 SQL 对 0 条。
在真实的 PostgreSQL 上实测乐观锁:无锁时丢失更新(期望 20 实际 10)、version_id_col 的 UPDATE 带版本条件与 StaleDataError、8 线程各加 20 次时无锁最终 25、乐观锁不重试 25、加重试 160(重试 727 次)、ETag 与 If-Match 返回 412。
在真实的 PostgreSQL 上实测悲观锁:with_for_update 的各种参数对应的 SQL、8 线程各加 20 次最终 160、nowait 与 skip_locked 与 lock_timeout 的行为、skip_locked 任务队列 9 个任务无重复、相反加锁顺序约 1 秒后 DeadlockDetected。