1. 实例的背景说明
假定一个个人信息系统,需要记录系统中各个人的故乡、居住地、以及到过的城市。数据库设计如下:
Models.py 内容如下:
from django.db import models class Province(models.Model): name = models.CharField(max_length=10) def __unicode__(self): return self.name class City(models.Model): name = models.CharField(max_length=5) province = models.ForeignKey(Province) def __unicode__(self): return self.name 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 __unicode__(self): return self.firstname + self.lastname
注1:创建的app名为“QSOptimize”
注2:为了简化起见,`qsoptimize_province` 表中只有2条数据:湖北省和广东省,`qsoptimize_city`表中只有三条数据:武汉市、十堰市和广州市
2. select_related()
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化
作用和方法
在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。以上例说明,如果我们需要打印数据库中的所有市及其所属省份,最直接的做法是:
> citys = City.objects.all() > for c in citys: ... print c.province ...
这样会导致线性的SQL查询,如果对象数量n太多,每个对象中有k个外键字段的话,就会导致n*k+1次SQL查询。在本例中,因为有3个city对象就导致了4次SQL查询:
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city` SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province` WHERE `QSOptimize_province`.`id` = 1 ; SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province` WHERE `QSOptimize_province`.`id` = 2 ; SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province` WHERE `QSOptimize_province`.`id` = 1 ;
注:这里的SQL语句是直接从Django的logger:‘django.db.backends'输出出来的
如果我们使用select_related()函数:
> citys = City.objects.select_related().all() > for c in citys: ... print c.province ...
就只有一次SQL查询,显然大大减少了SQL查询的次数:
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM`QSOptimize_city` INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;
这里我们可以看到,Django使用了INNER JOIN来获得省份的信息。顺便一提这条SQL查询得到的结果如下:
+----+-----------+-------------+----+-----------+ | id | name | province_id | id | name | +----+-----------+-------------+----+-----------+ | 1 | 武汉市 | 1 | 1 | 湖北省 | | 2 | 广州市 | 2 | 2 | 广东省 | | 3 | 十堰市 | 1 | 1 | 湖北省 | +----+-----------+-------------+----+-----------+ 3 rows in set (0.00 sec)
使用方法
函数支持如下三种用法:
*fields 参数
select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接。
例如我们要获得张三的现居省份,可以用如下方式:
> zhangs = Person.objects.select_related('living__province').get(firstname=u"张",lastname=u"三") > zhangs.living.province
触发的SQL查询如下:
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`.`living_id` = `QSOptimize_city`.`id`) INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) WHERE (`QSOptimize_person`.`lastname` = '三' AND `QSOptimize_person`.`firstname` = '张' );
可以看到,Django使用了2次 INNER JOIN 来完成请求,获得了city表和province表的内容并添加到结果表的相应列,这样在调用 zhangs.living的时候也不必再次进行SQL查询。
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+ | id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name | +----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+ | 1 | 张 | 三 | 3 | 1 | 1 | 武汉市 | 1 | 1 | 湖北省 | +----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+ 1 row in set (0.00 sec)
然而,未指定的外键则不会被添加到结果中。这时候如果需要获取张三的故乡就会进行SQL查询了:
> zhangs.hometown.province SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city` WHERE `QSOptimize_city`.`id` = 3 ; SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province` WHERE `QSOptimize_province`.`id` = 1
同时,如果不指定外键,就会进行两次查询。如果深度更深,查询的次数更多。
值得一提的是,从Django 1.7开始,select_related()函数的作用方式改变了。在本例中,如果要同时获得张三的故乡和现居地的省份,在1.7以前你只能这样做:
> zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"张",lastname=u"三") > zhangs.hometown.province > zhangs.living.province
但是1.7及以上版本,你可以像和queryset的其他函数一样进行链式操作:
> zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"张",lastname=u"三") > zhangs.hometown.province > zhangs.living.province
如果你在1.7以下版本这样做了,你只会获得最后一个操作的结果,在本例中就是只有现居地而没有故乡。在你打印故乡省份的时候就会造成两次SQL查询。
depth 参数
select_related() 接受depth参数,depth参数可以确定select_related的深度。Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey。以本例说明:
> zhangs = Person.objects.select_related(depth = d)
d=1 相当于 select_related(‘hometown','living')
d=2 相当于 select_related(‘hometown__province','living__province')
无参数
select_related() 也可以不加参数,这样表示要求Django尽可能深的select_related。例如:zhangs = Person.objects.select_related().get(firstname=u”张”,lastname=u”三”)。但要注意两点:
Django本身内置一个上限,对于特别复杂的表关系,Django可能在你不知道的某处跳出递归,从而与你想的做法不一样。具体限制是怎么工作的我表示不清楚。
Django并不知道你实际要用的字段有哪些,所以会把所有的字段都抓进来,从而会造成不必要的浪费而影响性能。
小结
- select_related主要针一对一和多对一关系进行优化。
- select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
- 可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
- 也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
- 也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
- Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]