量 CONN_MAX_AGE:300 个请求中位延迟 2.78 ms(默认)→ 0.65 ms(60 秒);默认 300 个请求 300 个连接,持久连接只用 1 个;但持久连接数 = worker × 线程,max_connections=30 的库上开第二个服务就出现 too many clients。
在 PostgreSQL 上记录 transaction.atomic() 的实际 SQL:外层 BEGIN/COMMIT、内层 SAVEPOINT/RELEASE;内层异常只回滚到保存点;atomic 里吞掉 IntegrityError 而不包内层 atomic 会导致后续查询全部 TransactionManagementError;durable 与 savepoint=False 的行为。
实测 transaction.on_commit():外层事务提交后才执行(此时别的连接已能看见数据);回滚与保存点回滚都会丢弃回调;autocommit 下立即执行;一个回调抛异常会阻止后面的回调,robust=True 则继续;回调里的写入处于 autocommit。
在 PostgreSQL 上做行锁实验:select_for_update 必须在 atomic 里;持锁期间普通调用阻塞约 1 秒、nowait=True 立即 OperationalError、skip_locked=True 返回 0 行;skip_locked 让 3 个线程并发消费 30 个任务恰好各一次;of 限定锁的表。
在 PostgreSQL 上实测三种隔离级别下的 Django 行为:默认 READ COMMITTED 会不可重复读(100 → 200);REPEATABLE READ 读到同一快照,但并发更新同一行会 OperationalError: could not serialize access due to concurrent update,需要应用重试。
实测 ATOMIC_REQUESTS:视图抛异常时整个视图的写入回滚,但返回 500 响应(不是异常)时照样提交;中间件的写入在事务之外;@non_atomic_requests 例外;on_commit 的时机随之改变;流式响应的生成器写入在事务之外。
在 100 万行的 PostgreSQL 表上量 Django 分页:Paginator 先发 COUNT(*)(14.1 ms)再发 LIMIT/OFFSET;第 1 页 0.26 ms,最后一页 42.5 ms(OFFSET 越深越慢);keyset 只要 0.32 ms;未排序会警告。
三个 app(people ← orders ← billing)实测迁移文件与依赖图:外键决定跨 app 依赖、同 app 后一个依赖前一个;showmigrations --plan 是拓扑排序后的计划;django_migrations 只记录已应用的迁移;MigrationLoader 的节点与叶子;循环依赖抛 CircularDependencyError。
逐项改动模型,看 makemigrations 检测到什么、sqlmigrate 有多少条 SQL:新增字段需要默认值或 NULL;help_text / choices 会生成 0 条 SQL 的迁移;字段改名在 --noinput 下被当作删除 + 新增;改名同时改类型永远是删除 + 新增;纯 Python 方法不产生迁移。
用三个互相依赖的 app 实测 migrate 的执行顺序:migrate --plan 是依赖 + 图遍历的结果;只迁移某个 app 时只补齐依赖;回退按反向依赖(先撤销依赖者);django_migrations 只存已应用记录;run_before 能让别的 app 的迁移排到自己后面。
实测 RunPython 数据迁移:apps.get_model 拿到的是没有自定义方法的历史模型;10 万行回填,一条 UPDATE(F / Concat)1.55 s,Python 循环 + bulk_update 2 万行就要 1.55 s;迁移里 import 真实模型会在全新库上因列不存在而失败。
实测迁移的正向与反向:RunPython 没有 reverse_code 时回退抛 IrreversibleError,加 RunPython.noop 才能回退;noop 反向不还原数据;RemoveField 回退只恢复列不恢复数据(值变成 default);DeleteModel 回退得到空表。