在模板里数 QuerySet 的查询次数:同一个 QuerySet 遍历多次只查 1 次;{% for p in posts.all %}、.first、.0 各自新查;遍历里访问外键或 __str__ 里用了外键是 21 次(1 + 20),select_related 后 1 次;a.posts.count / all 每个作者一次;prefetch_related 后 2 次;传 list 给模板 0 次。
实测 Django 缓存框架的基本行为:add 不覆盖、get_or_set、incr 对不存在的 key 抛 ValueError、touch 重置过期时间;timeout 的 0 / None / 负数语义;get 无法区分「没有」与「值为 None」(用 has_key);值以 pickle 副本存取。
对比四种缓存后端:读写耗时(locmem get 2.6 µs、redis get 90 µs、db set 350 µs);gunicorn 两个 worker 下 LocMem 只命中 11/20,其它后端 20/20;Django 重启后 LocMem 丢失;LocMem 默认最多 300 条。
直接看 Redis 里的 key 实测缓存 key 生成规则:前缀:版本:key(shop:1:user:1);KEY_FUNCTION 自定义;version 与 incr_version;含空格 / 过长 key 触发 CacheKeyWarning(Redis 上照常工作);同前缀同 key 的不同应用会互相覆盖。
实测页面缓存与低级缓存:cache_page 按 URL(含查询串)+ Vary 头区分,响应头带 Cache-Control: max-age;自定义头 Vary 有效,而 Accept-Language 在 USE_I18N 下不参与 key(没有 LocaleMiddleware 时 zh 拿到 en 的缓存);带 Set-Cookie 的响应被缓存会把同一 cookie 发给所有人;{% cache %} 片段缓存。
用线程 + 模拟数据库做缓存实验:穿透(同一不存在 key 100 次 100 → 1 次数据库调用,随机 key 仍 100 次);击穿(50 并发 50 → 1 次);雪崩(相同 TTL 250ms 窗口峰值 189,TTL 抖动后 122,但总调用数更多)。
实测缓存过期与并发:get_or_set 20 线程并发时 compute 执行 20 次;cache.add 互斥只算 1 次;计算 200 ms、TTL 1 秒时阻塞式回源 p99 205 ms,stale-while-revalidate p99 0.3 ms;set 重置 TTL;缓存里读-改-写会 lost update。
实测用 Redis 缓存 session:key 格式 :1:django.contrib.sessions.cache<key>、TTL 14 天、pickle 值;Redis 被清空后 cache 后端全员登出而 cached_db 不受影响;maxmemory + allkeys-lru 会把 session 一起淘汰;本机三种后端每请求耗时 0.41 / 0.41 / 0.40 ms(无差异)。
用真实的 manage.py test 演示查询次数测试:assertNumQueries 精确断言与 N+1 失败时列出每条 SQL 的报错;CaptureQueriesContext 上界断言;TestCase 里 atomic 是 SAVEPOINT、TransactionTestCase 里是 BEGIN / COMMIT。
在 admin 列表页上实战 N+1:admin 会自动对 list_display 里的外键做 select_related,但 __str__ 里更深的外键(author.category)仍然每行一次(55 次查询 / 50 行);list_select_related 之后固定 5 次;10/50/200 行的查询数 15/55/105(admin 每页 100 条)。
用 Django Debug Toolbar 的 SQL 面板看 N+1 页面:101 次查询、100 次 similar / 100 次 duplicate,并带调用栈;select_related 后只剩 1 次;同一页面有无工具栏的请求耗时 10.31 ms → 22.24 ms。
遍历 20 万行:list() 与直接 for 峰值内存 155.0 MiB,iterator() 1.7 MiB、chunk_size=500 时 0.5 MiB;PostgreSQL 上同样;iterator() 与 prefetch_related 一起用必须给 chunk_size;iterator() 之后不缓存。