如何高性能地使用django的orm

         在我的上一篇文章中《关于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)
 
 
object_list=[]
for i in range(100):
     object = t_table1()
     object.a_id = id
     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);
    
3. 删 :
单个数据删除:  
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_queryparams=Nonetranslations=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(‘
 
         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

 
所以对于不含外键的大系统,原生的sql语句是没法绕开的一道坎,必须要使用的。
至于含有外键的小系统,只用orm已经足够。
现如今我做的所有大系统都是orm和sql并用的。
我的原则是: 简单查询使用orm,因为足够方便。复杂查询使用sql语句,因为足够强大。
 
 
 
 
 
 
posted on 2020-03-05 11:35  摩登海贼  阅读(309)  评论(0编辑  收藏  举报