这是本系列的最后一篇,主要是select_related() 和 prefetch_related() 的最佳实践。
主篇这里
第一篇在这里 讲例子和select_related()
第二篇在这里 讲prefetch_related()
-
>>> hb = Province.objects.get(name__iexact=
u"湖北省")
-
>>> people = []
-
>>>
for city
in hb.city_set.all():
-
... people.extend(city.birth.all())
-
...
显然这不是一个明智的选择,因为这样做会导致1+(湖北省城市数)次SQL查询。反正是个反例,导致的查询和获得掉结果就不列出来了。
-
>>> hb = Province.objects.prefetch_related(
"city_set__birth").objects.get(name__iexact=
u"湖北省")
-
>>> people = []
-
>>>
for city
in hb.city_set.all():
-
... people.extend(city.birth.all())
-
...
因为是一个深度为2的prefetch,所以会导致3次SQL查询:
-
SELECT
`QSOptimize_province`.
`id`,
`QSOptimize_province`.
`name`
-
FROM
`QSOptimize_province`
-
WHERE
`QSOptimize_province`.
`name`
LIKE
'湖北省' ;
-
-
SELECT
`QSOptimize_city`.
`id`,
`QSOptimize_city`.
`name`,
`QSOptimize_city`.
`province_id`
-
FROM
`QSOptimize_city`
-
WHERE
`QSOptimize_city`.
`province_id`
IN (
1);
-
-
SELECT
`QSOptimize_person`.
`id`,
`QSOptimize_person`.
`firstname`,
`QSOptimize_person`.
`lastname`,
-
`QSOptimize_person`.
`hometown_id`,
`QSOptimize_person`.
`living_id`
-
FROM
`QSOptimize_person`
-
WHERE
`QSOptimize_person`.
`hometown_id`
IN (
1,
3);
>>> people = list(Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北省"))
-
SELECT
`QSOptimize_person`.
`id`,
`QSOptimize_person`.
`firstname`,
`QSOptimize_person`.
`lastname`,
-
`QSOptimize_person`.
`hometown_id`,
`QSOptimize_person`.
`living_id`,
`QSOptimize_city`.
`id`,
-
`QSOptimize_city`.
`name`,
`QSOptimize_city`.
`province_id`,
`QSOptimize_province`.
`id`,
`QSOptimize_province`.
`name`
-
FROM
`QSOptimize_person`
-
INNER
JOIN
`QSOptimize_city`
ON (
`QSOptimize_person`.
`hometown_id` =
`QSOptimize_city`.
`id`)
-
INNER
JOIN
`QSOptimize_province`
ON (
`QSOptimize_city`.
`province_id` =
`QSOptimize_province`.
`id`)
-
WHERE
`QSOptimize_province`.
`name`
LIKE
'湖北省';
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name | +----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+ | 1 | 张 | 三 | 3 | 1 | 3 | 十堰市 | 1 | 1 | 湖北省 |
| 2 | 李 | 四 | 1 | 3 | 1 | 武汉市 | 1 | 1 | 湖北省 | | 3 | 王 | 麻子 | 3 | 2 | 3 | 十堰市 | 1 | 1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
3 rows in set (0.00 sec)
完全没问题。不仅SQL查询的数量减少了,python程序上也精简了。
-
class Order(models.Model):
-
customer = models.ForeignKey(Person)
-
orderinfo = models.CharField(max_length=
50)
-
time = models.DateTimeField(auto_now_add =
True)
-
def __unicode__(self):
-
return self.orderinfo
如果我们拿到了一个订单的id 我们要知道这个订单的客户去过的省份。因为有ManyToManyField显然必须要用prefetch_related()。如果只用prefetch_related()会怎样呢?
-
>>> plist = Order.objects.prefetch_related(
'customer__visitation__province').get(id=
1)
-
>>>
for city
in plist.customer.visitation.all():
-
...
print city.province.name
-
...
显然,关系到了4个表:Order、Person、City、Province,根据prefetch_related()的特性就得有4次SQL查询
-
SELECT
`QSOptimize_order`.
`id`,
`QSOptimize_order`.
`customer_id`,
`QSOptimize_order`.
`orderinfo`,
`QSOptimize_order`.
`time`
-
FROM
`QSOptimize_order`
-
WHERE
`QSOptimize_order`.
`id` =
1 ;
-
-
SELECT
`QSOptimize_person`.
`id`,
`QSOptimize_person`.
`firstname`,
`QSOptimize_person`.
`lastname`,
`QSOptimize_person`.
`hometown_id`,
`QSOptimize_person`.
`living_id`
-
FROM
`QSOptimize_person`
-
WHERE
`QSOptimize_person`.
`id`
IN (
1);
-
-
SELECT (
`QSOptimize_person_visitation`.
`person_id`)
AS
`_prefetch_related_val`,
`QSOptimize_city`.
`id`,
-
`QSOptimize_city`.
`name`,
`QSOptimize_city`.
`province_id`
-
FROM
`QSOptimize_city`
-
INNER
JOIN
`QSOptimize_person_visitation`
ON (
`QSOptimize_city`.
`id` =
`QSOptimize_person_visitation`.
`city_id`)
-
WHERE
`QSOptimize_person_visitation`.
`person_id`
IN (
1);
-
-
SELECT
`QSOptimize_province`.
`id`,
`QSOptimize_province`.
`name`
-
FROM
`QSOptimize_province`
-
WHERE
`QSOptimize_province`.
`id`
IN (
1,
2);
+----+-------------+---------------+---------------------+
| id | customer_id | orderinfo | time | +----+-------------+---------------+---------------------+ | 1 | 1 | Info of Order | 2014-08-10 17:05:48 |
+----+-------------+---------------+---------------------+
1 row in set (0.00 sec)
±—±----------±---------±------------±----------+
| id | firstname | lastname | hometown_id | living_id |
±—±----------±---------±------------±----------+
| 1 | 张 | 三 | 3 | 1 |
±—±----------±---------±------------±----------+
1 row in set (0.00 sec)
±----------------------±—±-------±------------+
| _prefetch_related_val | id | name | province_id |
±----------------------±—±-------±------------+
| 1 | 1 | 武汉市 | 1 |
| 1 | 2 | 广州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
±----------------------±—±-------±------------+
3 rows in set (0.00 sec)
±—±-------+
| id | name |
±—±-------+
| 1 | 湖北省 |
| 2 | 广东省 |
±—±-------+
2 rows in set (0.00 sec)
-
>>> plist = Order.objects.select_related(
'customer').prefetch_related(
'customer__visitation__province').get(id=
1)
-
>>>
for city
in plist.customer.visitation.all():
-
...
print city.province.name
-
...
这样只会有3次SQL查询,Django会先做select_related,之后prefetch_related的时候会利用之前缓存的数据,从而避免了1次额外的SQL查询:
-
SELECT
`QSOptimize_order`.
`id`,
`QSOptimize_order`.
`customer_id`,
`QSOptimize_order`.
`orderinfo`,
-
`QSOptimize_order`.
`time`,
`QSOptimize_person`.
`id`,
`QSOptimize_person`.
`firstname`,
-
`QSOptimize_person`.
`lastname`,
`QSOptimize_person`.
`hometown_id`,
`QSOptimize_person`.
`living_id`
-
FROM
`QSOptimize_order`
-
INNER
JOIN
`QSOptimize_person`
ON (
`QSOptimize_order`.
`customer_id` =
`QSOptimize_person`.
`id`)
-
WHERE
`QSOptimize_order`.
`id` =
1 ;
-
-
SELECT (
`QSOptimize_person_visitation`.
`person_id`)
AS
`_prefetch_related_val`,
`QSOptimize_city`.
`id`,
-
`QSOptimize_city`.
`name`,
`QSOptimize_city`.
`province_id`
-
FROM
`QSOptimize_city`
-
INNER
JOIN
`QSOptimize_person_visitation`
ON (
`QSOptimize_city`.
`id` =
`QSOptimize_person_visitation`.
`city_id`)
-
WHERE
`QSOptimize_person_visitation`.
`person_id`
IN (
1);
-
-
SELECT
`QSOptimize_province`.
`id`,
`QSOptimize_province`.
`name`
-
FROM
`QSOptimize_province`
-
WHERE
`QSOptimize_province`.
`id`
IN (
1,
2);
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| id | customer_id | orderinfo | time | id | firstname | lastname | hometown_id | living_id |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| 1 | 1 | Info of Order | 2014-08-10 17:05:48 | 1 | 张 | 三 | 3 | 1 |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
±----------------------±—±-------±------------+
| _prefetch_related_val | id | name | province_id |
±----------------------±—±-------±------------+
| 1 | 1 | 武汉市 | 1 |
| 1 | 2 | 广州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
±----------------------±—±-------±------------+
3 rows in set (0.00 sec)
±—±-------+
| id | name |
±—±-------+
| 1 | 湖北省 |
| 2 | 广东省 |
±—±-------+
2 rows in set (0.00 sec)
值得注意的是,可以在调用prefetch_related之前调用select_related,并且Django会按照你想的去做:先select_related,然后利用缓存到的数据prefetch_related。然而一旦prefetch_related已经调用,select_related将不起作用。