有次在生产系统中,PG日志输出一条超过3秒的简单查询SQL,经分析这个SQL生成的过程,觉得有点参考意义,现提供给大家参考。
先介绍一下这个SQL用到的两个数据模型定义:
model A:
name:姓名字段(char)
mobile:电话号码字段(char)
state:状态字段(selection)
reports:报告附件(one2many,关联model B)
model B:
parent_id:主表ID(many2one,关联model A)
name:报告名称(char)
......
在其中一个业务方法(定义在model A)中,用到了如下的一条查询:
self.search([("mobile","=", "1380013800"),("name","=","张三"),("id","!=", self.id),("state","!=","cancel"),("reports","!=",False)])
这个查询的作用,是检测姓名+电话相同的客户,之前是否已经有记录,并且有对应的报告附件。
然后在PG日志中,我看根据ORM生成的SQL语法是这样的:
SELECT "model_A".idFROM "model_A"WHERE (((((("model_A"."active" = true)AND ("model_A"."mobile" = '1380013800'))AND ("model_A"."name" = '张三'))AND "model_A".id != 432814)AND (("model_A"."state" not in ('cancel','reuse'))OR "model_A"."state" IS NULL))AND ("model_A"."id" in (/*许许多多的id*/)))ORDER BY "model_A"."id" DESC
这个单表查询,用到了id的索引,本来应该是比较快的,但因为莫名的多了一个id in (大量id)的条件,造成速度变慢。一般走索引的话,返回少量的记录才会提升性能,如果通过索引返回大量数据,则性能反而会变慢。
这里我们就要考虑这么多的id是哪里来的?另外domain中有一个('reports','!=',False),并没有在SQL中反应出来,所以我们继续在odoo中追踪生成的SQL语句,发现这个search其实是生成了两条SQL语句,第一条是:
select distinct(parent_id) from model_B where parent_id != 432814
这条SQL是从model B中查询出所有的已经有附件的主表id,然后odoo把从这里取到的id放到上一条in条件中。所以这里in条件里的id都是已经有报告附件的记录,就可以对应到domain中的('reports','!=',False)条件。
因为我们model B中有十多万笔记录,这个distinct(parent_id)的结果就会比较多,造成上面的SQL在in条件中有太多的id值。查询速度显著降低。
现在我们不能改ORM底层生成SQL的逻辑,所以就把上面search的语法改为:
self.search([("mobile","=", "1380013800"),("name","=","张三"),("id","!=", self.id),("state","!=","cancel")]).filtered(lambda l: l.reports)
这里search的domain中,去掉reports条件,根据其它条件获取记录以后,再通过ORM的filtered方法,设置过滤条件,只过滤reports有值的记录。
因为根据前面的条件,一般也只会查询到一两笔记录,在这一两笔记录中再通过filtered去查询model B就会很快了。
通过这个案例,我们需要建议一条odoo开发的军规,严禁在domain中使用one2many或many2many的条件。




