大字段不想每次都查,用 only() 或 defer() 就完事了吗?一旦你后面又读到了它,会发生什么?这篇数出额外的查询次数,并量不同取法的耗时。
all() SELECT "id", "title", "body", "author_id"
only('title') SELECT "id", "title"
defer('body') SELECT "id", "title", "author_id"
only('title', 'author__name')+select_related SELECT "id", "title", "author_id", "lab_author"."id", "lab_author"."name" FROM "lab_article" INNER JOIN "lab_author" ON ("author_id" = "lab_author"."id") LIMIT 1
only('title') 的实例 get_deferred_fields() = ['author_id', 'body']
defer('body') 的实例 get_deferred_fields() = ['body']only() 是白名单(主键总是会取),defer() 是黑名单。only('title') 连外键 author_id 也推迟了,访问 a.author_id 或 a.author 都会额外查询。
200 行 all() 读 body 查询 1 次 0.9 ms
200 行 only('title') 只读 title 查询 1 次 0.5 ms
200 行 only('title') 又读了 body 查询 201 次 22.9 ms模板里循环访问一个被 defer 的字段,是这个坑的高发场景。发现时用 only() 列全要用的字段,或直接 values() / values_list()。
Article.objects.all()(取全部字段) 查询 1 次 12.5 ms
defer('body') 查询 1 次 8.9 ms
only('title') 查询 1 次 5.1 ms
values_list('title', flat=True) 查询 1 次 1.2 msSQLite 在本机,没有网络往返,所以字段体积带来的差别主要是读取和构造对象的代价;换成远程数据库,大字段的传输代价会更明显(本文没有测)。
only("title") 实例 save() 的 SQL: UPDATE "lab_article" SET "title" = 'changed' WHERE "lab_article"."id" = 1
refresh_from_db(fields=["body"]) 的 SQL: SELECT "lab_article"."id", "lab_article"."body" FROM "lab_article" WHERE "lab_article"."id" = 1 LIMIT 21 | 之后 deferred = ['author_id']对推迟了字段的实例调用 save(),Django 只更新已加载的字段,不会误把推迟的字段写掉。