本文中使用Python 2.7.15、Django 1.8.19、MariaDB 5.5.62,本文设计大量Django生成的SQL,为方便阅读,对SQL进行了部分简化。
最近开始使用Django,Django的Model功能性非常简单,可以完全不关心SQL,与另一个流行的Python ORM工具SQLAlchyma形成强烈的对比。但是如果只使用Django Model的简单特性,其SQL性能是惨不忍睹的。所幸的是,Django还是提供了一些进阶工具给大家使用。
如果想优化Django的SQL性能,第一步是打开SQL日志,打开SQL日志需要在serrings.LOGGING.loggers字典中加如如下字典项:
1 2 3 4 5
   | 'django.db.backends': {     'handlers': ['console'],     'propagate': True,     'level':'DEBUG', },
  | 
 
在本例中,创建两个简单的模型如下:
1 2 3 4 5 6
   | class City(models.Model):     name = models.CharField(max_length=100)
  class Person(models.Model):     name = models.CharField(max_length=100)     city = models.ForeignKey(City, related_name="person")
   | 
 
并在MySQL中写入如下测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | demo_city表 +----+----------+ | id | name     | +----+----------+ | 1  | ShenZhen | | 2  | BeiJing  | +----+----------+ demo_person表 +----+--------+---------+ | id | name   | city_id | +----+--------+---------+ | 1  | Tom    | 2       | | 2  | Jerry  | 2       | | 3  | Snow   | 2       | | 4  | Frank  | 2       | | 5  | Bluice | 2       | | 6  | Dave   | 1       | | 7  | Alen   | 1       | | 8  | Iris   | 1       | | 9  | Ella   | 1       | | 10 | Eva    | 1       | +----+--------+---------+
   | 
 
如果我们要获取id=1的用户的城市信息,我们一般很容易写出如下的代码。
1 2
   | person = Person.objects.filter(id=1).first() print person.city.name
   | 
 
虽然我们看起来只做了一次查询,此时请求了两次数据库:
1 2
   | SELECT * FROM `demo_person` WHERE `demo_person`.`id` = 1 LIMIT 1; SELECT * FROM `demo_city` WHERE `demo_city`.`id` = 2 LIMIT 1
   | 
 
如果我们要打印所有的人和城市的列表,用一个循环可以很好的解决:
1 2 3
   | persons = Person.objects.all() for person in persons:     print person.name, person.city.name
   | 
 
我们不幸的请求了11次数据库:
1 2 3 4 5 6 7 8 9 10 11
   | SELECT * FROM `demo_person`; SELECT * WHERE `demo_city`.`id` = 2; SELECT * WHERE `demo_city`.`id` = 2; SELECT * WHERE `demo_city`.`id` = 2; SELECT * WHERE `demo_city`.`id` = 2; SELECT * WHERE `demo_city`.`id` = 2; SELECT * WHERE `demo_city`.`id` = 1; SELECT * WHERE `demo_city`.`id` = 1; SELECT * WHERE `demo_city`.`id` = 1; SELECT * WHERE `demo_city`.`id` = 1; SELECT * WHERE `demo_city`.`id` = 1;
   | 
 
而且其中有相当多的完全一样的SQL,列表查询中这是一个不可忽视的性能杀手。熟悉SQL应该了解,对多个关联表进行查询可以使用join关键字,在Django中我们可以使用.select_related()方法:
1 2 3
   | persons = Person.objects.select_related("city").all() for person in persons:     print person.name, person.city.name
  | 
 
几乎和原来一模一样的代码,这次只生成了一个SQL:
1 2
   | SELECT `* FROM `demo_person`  INNER JOIN `demo_city` ON ( `demo_person`.`city_id` = `demo_city`.`id` );
   | 
 
.select_related()通常情况下可以不加参数,Django的“懒查询”机制会自动会自动处理关联的表,但是有时(尤其是多表递归关联和Django多对多关系)会无效,需要手动处理。点击查看详情
上面的的还有一种预取查询的方案,写法如下:
1 2 3
   | person = Person.objects.prefetch_related('city').all() for person in persons: 	print person.name, person.city.name
  | 
 
生成的SQL如下:
1 2
   | SELECT * FROM `demo_person`; SELECT * FROM `demo_city` WHERE `demo_city`.`id` IN (1, 2);
   | 
 
prefetch_related可以控制“子查询”的行为,点击查看详情
和子查询的语法非常类似,性能应该也类似于子查询,也大大减少了查询的数量。组合这两种方法,可以获得数倍的性能提升。尤其是在查询
虽然join、in在复杂查询时也是性能杀手,但比起循环查询数据库带来的IO开销,就是小巫见大巫了。