环境:
pip install django==1.8.3
pip install django-debug-toolbar==1.9.1
mysql版本:5.7.22
models.py
# -*- coding: utf-8 -*-
from django.db import models
class Province(models.Model):
name = models.CharField(max_length=10)
def __str__(self):
return self.name
class Meta:
db_table = 'Province'
class City(models.Model):
name = models.CharField(max_length=5)
province = models.ForeignKey(Province, related_name="city_set")
def __str__(self):
return self.name
class Meta:
db_table = 'City'
class Person(models.Model):
firstname = models.CharField(max_length=10)
lastname = models.CharField(max_length=10)
visitation = models.ManyToManyField(City, related_name="visitor")
hometown = models.ForeignKey(City, related_name="birth")
living = models.ForeignKey(City, related_name="citizen")
def __str__(self):
return self.firstname + self.lastname
class Meta:
db_table = 'Person'
创建表及插入数据
百度云盘mysql文件下载地址:https://pan.baidu.com/s/1Jv8dYU98b_667b8NvNKnNg
python manage.py makemigrations
python manage.py migrate
测试数据如上`mysql文件下载`, 在mysql中执行导入命令:
source test.sql;
如果导入过程报错,可能有外键约束,在mysql中执行:
set foreign_key_checks = 0;
也可自行创建数据。django插入示例如下:
obj = Person.objects.create(
firstname='王',
lastname='五',
hometown_id=2,
living_id=5
)
obj.save()
obj.visitation.add(c_objs[0])
obj.visitation.add(c_objs[3])
测试
>>> from mytest_app.models import *
# 获取张三游览过的城市
>>> Person.objects.prefetch_related('visitation').get(firstname=u"张", lastname=u"三").visitation.all() # 获 取张三游览过的城市
SELECT `Person`.`id`,
`Person`.`firstname`,
`Person`.`lastname`,
`Person`.`hometown_id`,
`Person`.`living_id`
FROM `Person`
WHERE (`Person`.`lastname` = '三'
AND `Person`.`firstname` = '张') [1.46ms]
SELECT (`Person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`,
`City`.`id`,
`City`.`name`,
`City`.`province_id`
FROM `City`
INNER JOIN `Person_visitation` ON (`City`.`id` = `Person_visitation`.`city_id`)
WHERE `Person_visitation`.`person_id` IN (3) [0.48ms]
[<City: 武汉>, <City: 咸宁>, <City: 上海>]
如上,会生成2条sql, 下面再看正向查询:
>>> City.objects.filter(visitor__firstname=u'张', visitor__lastname=u'三')
SELECT `City`.`id`,
`City`.`name`,
`City`.`province_id`
FROM `City`
INNER JOIN `Person_visitation` ON (`City`.`id` = `Person_visitation`.`city_id`)
INNER JOIN `Person` ON (`Person_visitation`.`person_id` = `Person`.`id`)
WHERE (`Person`.`lastname` = '三'
AND `Person`.`firstname` = '张')
LIMIT 21 [1.60ms]
[<City: 武汉>, <City: 咸宁>, <City: 上海>]
正向查询只需要一条sql,但是当表数据很多的时候,会出现性能问题
再来看看 select_related 和 prefetch_related 对比:
# 获得所有家乡是湖北的人
# prefetch_related 查询
>>> hb = Province.objects.prefetch_related("city_set__birth").get(name__iexact=u"湖北")
SELECT `Province`.`id`,
`Province`.`name`
FROM `Province`
WHERE `Province`.`name` LIKE '湖北' [0.68ms]
SELECT `City`.`id`,
`City`.`name`,
`City`.`province_id`
FROM `City`
WHERE `City`.`province_id` IN (1) [0.31ms]
SELECT `Person`.`id`,
`Person`.`firstname`,
`Person`.`lastname`,
`Person`.`hometown_id`,
`Person`.`living_id`
FROM `Person`
WHERE `Person`.`hometown_id` IN (1,
2,
3) [0.40ms]
>>> people = []
>>> for city in hb.city_set.all():
... people.extend(city.birth.all())
...
>>> print(people)
[<Person: 李悦>, <Person: 李飞>, <Person: 张三>, <Person: 王五>, <Person: 李四>]
# select_related 查询
>>> Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北")
SELECT `Person`.`id`,
`Person`.`firstname`,
`Person`.`lastname`,
`Person`.`hometown_id`,
`Person`.`living_id`,
`City`.`id`,
`City`.`name`,
`City`.`province_id`,
`Province`.`id`,
`Province`.`name`
FROM `Person`
INNER JOIN `City` ON (`Person`.`hometown_id` = `City`.`id`)
INNER JOIN `Province` ON (`City`.`province_id` = `Province`.`id`)
WHERE `Province`.`name` LIKE '湖北'
LIMIT 21 [0.38ms]
[<Person: 李悦>, <Person: 李飞>, <Person: 张三>, <Person: 李四>, <Person: 王五>]
- select_related()的效率要高于prefetch_related()。因此,最好在能用select_related()的地方尽量使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
- select_related 只能用于一对一查询,不能用于多对多,而 prefetch_related 则都行,只是都是分别查询。
- prefetch_related 相当于语言层面的连接查询,写起来跟 select_related 一样,好像是做了表之间的连接,但是它实际上的解决方法是,分别查询每一个表,然后用Python处理他们之间的关系。
- 你可以在一个QuerySet中同时使用select_related()和prefetch_related(),从而减少SQL查询的次数。但是只有prefetch_related()之前的select_related()是有效的,之后的将会被无视掉。
相关参考文档:
实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(二)
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
Django的select_related 和 prefetch_related 函数优化查询
网友评论