在我的上一篇文章中《关于django项目是否要使用外键》中总结过,对于小系统,是建议大家尽量地使用外键,因为它的易用性和便利性;对于大系统,最好不要使用外键,因为要考虑到系统的性能,可移植性等。同时引出了如何高性能地使用orm的问题,我下面会对小系统(使用外键)和大系统(不使用外键)的情况下如何使用orm的问题进行说明分析。
django的orm是非常强大的,它把数据库相关的操作都隔离了开来,让我们能够像访问普通的变量一样去访问数据库的数据,所以django是非常适合新手来使用,功能足够强大。当时orm也带来很多问题,过于高度的封装,忽略了效率,导致执行效率很差,所以如何使用高效地orm也是很重要的。一般来说,我们的接口都是不外乎是一个数据表的’增删查改‘,所以我就分别对这几个操作进行例子化分。
先假设表 t_table1, 拥有一个外键a
1. 查: 这里可以分为 单个数据查询, 以及 列表查询
1.1 单个数据查询:
通常的代码是 object = t_table1.objects.filter(id=1).first()
在我们执行了这句代码之后,获取到object对象,它实际上执行的sql语句并不是一句,而是2句:
select * from table1 where id=id
select * from a where id=id
从sql的效果来看,这个效率很低,其实这2句sql语句可以优化为一句,利用连表的形式进行多表查询。
上面代码可以优化为这样:object = t_table1.objects.select_related().filter(id=1).first()
加了一个select_related(), 那么实际执行的sql语句就变成了
select * from table1
INNER JOIN a on a.id=table1.a_id
where id=id
执行效率会高很多,试想一下,假如这里有10个外键,那么效率提升了就非常大了
1.2 列表查询
通常的列表查询的代码:
objects = t_table1.objects.all()
ret = [ model_to_dict(i) for i in objects]
它实际上执行的sql语句数量是非常庞大的,假如t_table1数据一共有100条,参考上面单个数据数据的执行方式,
它实际的执行是这样的:
for i in range(100):
select * from table1 where id=id
select * from a where id=id
这个就是orm的不足,按照上面ret = [ model_to_dict(i) for i in objects] 这句代码,它是每循环一次就执行一次sql语句。
等于执行了200条的sql语句。
如果t_table1 有10000条数据,有10个外键,那么就等于执行了20万条sql语句,简直是不可想象。
优化方案:
把代码改为
ret = t_table1.objects.select_related().all().values()
这个values() 这个方法我就不详细说,有兴趣可以查查资料 https://www.cnblogs.com/rgxx/p/10382664.html
改为这样之后,它实际上执行的sql语句是从20万条sql,变成了1条,效率优化非常明显:
select * from table1
INNER JOIN a on a.id=table1.a_id
2. 增
新增单个数据,通常的代码是:
t_table1.objects.create(a_id=id)
或者是
object = t_table1()
object.a_id = id
object.save()
批量创建:在批量创建数据的时候,使用这种方式就会有效率的问题
假如要创建100个数据:
for i in range(100):
t_table1.objects.create(a_id=id)
这样实际上执行的sql语句就会变成了100条:
for i in range(100):
insert into t_table1(a_id) values(id)
优化方案:使用 bulk_create 进行批量创建. https://www.cnblogs.com/ccorz/p/Django-model-zhong-shu-ju-pi-liang-dao-rubulkcreat.html
object_list=[]
for i in range(100):
for i in range(100):
object = t_table1()
object.a_id = id
object_list.append(object)
t_table1.objects.bulk_create(object_list)
object_list.append(object)
t_table1.objects.bulk_create(object_list)
优化后执行的sql语句只有一条,效率提高了不知道多少倍:
insert into t_table1(a_id) values(id1),(id2), (id3), (id4), (id5), .... (id100);
单个数据删除:
t_table1.objects.filter(id=id).delete()
或者是
object = t_table1.objects.filter(id=id).first()
object.delete()
批量删除:
这里要注意的是,假如需要删除全部数据,参考上面查询的优化项,不要进行循环删除:
objects = t_table1.objects.all()
for i in objects:
objects.delete()
如果执行这样的代码,会造成执行了非常多的sql语句,假如这样有100条数据,等于执行下面的sql语句
for i in range(100):
select * from table1 where id=id
select * from a where id=id
delete from table1 where id=id
等于执行了300条的sql语句,好恐怖
优化方案:
t_table1.objects.all().delete 即可, 这样就只是会执行一句的sql语句:
delete from table1 where id=id
4. 改:
单个数据修改:
t_table1.objects.filter(id=id).update(a_id=id)
或者是
object = t_table1.objects.filter(id=id).first()
object.a_id=id
object.save()
批量修改:
这里要注意的是,假如需要批量修改或者修改全部数据,参考上面查询的优化项,不要进行循环修改:
objects = t_table1.objects.all()
for i in objects:
i.a_id=id
objects.save()
如果执行这样的代码,会造成执行了非常多的sql语句,假如这样有100条数据,等于执行下面的sql语句
for i in range(100):
select * from table1 where id=id
select * from a where id=id
update table1 set a_id=id where id=id
等于执行了300条的sql语句
优化方案:
直接使用 t_table1.objects.all().update(a_id=id)
上面的4个总结是在使用orm的时候比较通用的经验,无论是小系统(有外键)和大系统(无外键)都适用。
5. 复杂的查询条件:
对于一个比较成熟的系统来说,肯定会遇到需要非常复杂的搜索条件的需求,例如bug系统中,会有很多筛选项,状态,处理人,创建时间等等。查询条件是一个非常复杂的一个组成。
对于拥有外键的小系统来说,这些复杂的条件都可以使用 Q 这个对象来构造查询条件,这里有参考资料:
例如需要查询id > 5 或者是 a的id > 2 的数据;
t_table1.objects.select_related().filter(Q(id>5 | Q(a__id>2))
这里变换为sql语句就是:
select * from table1
INNER JOIN a on a.id=table1.a_id
where id>5 and a.id>2
但是对于没有外键的大系统来说,是没法使用Q来构造条件的,因为Q有一种语法是 “__” ,这种是专门用于外键的。
举个例子,上面这句代码
t_table1.objects.select_related().filter(Q(id>5 | Q(a__id>2)),a 这个字段是t_table1的外键,它能够通过 a__xxx, 来访问a对应的表的属性;
比如a, 有一个叫name的字符串的属性,我想筛选t_table1 中 的a 的name等于‘hello’的数据,那么可以这么写:
t_table1.objects.select_related().filter(Q(a__name='hello')
但是假如没有外键,我们是不能这么做的,想想假如我们不用外键,会怎么定义t_table1表就明白了,我们会把表定义为:
class t_table1:
a_id=models.IntegerField()
那么这时候我们应该怎么做,想要实现这么复杂的查询,我们只能借助于编写原始sql语句。
使用sql语句有两种方式:
1、
Manager.
raw
(raw_query, params=None, translations=None)刚刚的筛选语句可以改为:
t_table1.objects.raw('
select * from table1
INNER JOIN a on a.id=table1.a_id
where id>5 and a.id>2
'):2. 使用 Executing custom SQL directly
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
#执行sql语句
cursor.execute(‘
def my_custom_sql(self):
with connection.cursor() as cursor:
#执行sql语句
cursor.execute(‘
select * from table1
INNER JOIN a on a.id=table1.a_id
where id>5 and a.id>2
’)
#查出一条数据
row = cursor.fetchone()
#查出所有数据
#row = cursor.fetchall()
return row
#查出一条数据
row = cursor.fetchone()
#查出所有数据
#row = cursor.fetchall()
return row
所以对于不含外键的大系统,原生的sql语句是没法绕开的一道坎,必须要使用的。
至于含有外键的小系统,只用orm已经足够。
现如今我做的所有大系统都是orm和sql并用的。
我的原则是: 简单查询使用orm,因为足够方便。复杂查询使用sql语句,因为足够强大。