在我编写一种新的 ORM 时,我更深入的研究了 Python ORM 的设计,这让我有了足够的知识来支撑起两年前的一个序列化 Django Model 的想法。
一般来说序列化 Django Model 都会使用 django-rest-framework 的序列化功能。使用它,你首先需要根据需求定义一个 Serializer 模型,然后通过这个模型去序列化 Django Model,它通常意味着你需要同时维护两套模型。而且在做复杂的跨表序列化操作时,这很容易导致 N+1 查询。
使用 Django ORM 的人都知道,Django 是有 prefetch_related 和 select_related 两个预查询功能来处理跨表查询中的 N+1 查询问题。那么预查询出来的模型缓存在哪儿了呢?
from typing import Any, Dict, List
from django.core.exceptions import FieldDoesNotExist
from django.db import models
def serialize_model(model: models.Model) -> Dict[str, Any]:
serialized = set()
def _serialize_model(model: models.Model) -> Dict[str, Any]:
if model in serialized:
return model.pk
else:
serialized.add(model)
result: Dict[str, Any] = {
name: _serialize_model(foreign_key)
for name, foreign_key in model.__dict__["_state"]
.__dict__.get("fields_cache", {})
.items()
if foreign_key is not None
}
for name, value in model.__dict__.items():
try:
model._meta.get_field(name)
except FieldDoesNotExist:
# 非模型字段
continue
else:
result[name] = value
for name, queryset in model.__dict__.get(
"_prefetched_objects_cache", {}
).items():
result[name] = serialize_queryset(queryset)
return result
return _serialize_model(model)
def serialize_queryset(queryset: models.QuerySet) -> List[Dict[str, Any]]:
return [serialize_model(model) for model in queryset]
在我研究之后发现 prefetch_related
预查询出来的结果会作为 QuerySet
对象存放在 __dict__["_prefetched_objects_cache"]
这个字典中字段名对应的键下;select_related
预查询出来的结果会存放在 __dict__["_state"].__dict__["fields_cache"]
中字段名对应的键下。
于是这就有了上面的代码。它不会自做主张的去查询数据库,只用你查询出来的结果,成功避免了 N+1 查询问题。通过查询时的 defer 和 only 操作可以控制哪些模型字段被查询,通过预查询语句可以控制哪些外键字段被查询。它不需要你维护另一套模型,你只需要专注于数据库模型正确即可。