在QuerySet上排除结果时效率低下的SQL查询

时间:2013-06-11 08:32:11

标签: python sql django django-orm

我想弄清楚为什么django ORM有这么奇怪(我认为)的行为。我有2个基本模型(简化以获得主要想法):

class A(models.Model):
    pass

class B(models.Model):
    name = models.CharField(max_length=15)
    a = models.ForeignKey(A)

现在我想从表a中选择从列b引用但在列名中没有值的行。 以下是我希望Django ORM生成的示例SQL:

SELECT * FROM inefficient_foreign_key_exclude_a a
INNER JOIN inefficient_foreign_key_exclude_b b ON a.id = b.a_id
WHERE NOT (b.name = '123');

如果filter() django.db.models.query.QuerySet方法符合预期:

>>> from inefficient_foreign_key_exclude.models import A
>>> print A.objects.filter(b__name='123').query
SELECT `inefficient_foreign_key_exclude_a`.`id`
FROM `inefficient_foreign_key_exclude_a`
INNER JOIN `inefficient_foreign_key_exclude_b` ON (`inefficient_foreign_key_exclude_a`.`id` = `inefficient_foreign_key_exclude_b`.`a_id`)
WHERE `inefficient_foreign_key_exclude_b`.`name` = 123

但是如果我使用exclude()方法(在底层逻辑中使用Q形式的负面形式),它会创建一个非常奇怪的SQL查询:

>>> print A.objects.exclude(b__name='123').query
SELECT `inefficient_foreign_key_exclude_a`.`id`
FROM `inefficient_foreign_key_exclude_a`
WHERE NOT ((`inefficient_foreign_key_exclude_a`.`id` IN (
    SELECT U1.`a_id` FROM `inefficient_foreign_key_exclude_b` U1 WHERE (U1.`name` = 123  AND U1.`a_id` IS NOT NULL)
) AND `inefficient_foreign_key_exclude_a`.`id` IS NOT NULL))

为什么ORM会创建子查询而不仅仅是JOIN?

更新

我做了一个测试来证明使用子查询根本没有效率。 我在ab表中创建了500401行。在这里我得到了:

加入:

mysql> SELECT count(*)
    -> FROM inefficient_foreign_key_exclude_a a
    -> INNER JOIN inefficient_foreign_key_exclude_b b ON a.id = b.a_id
    -> WHERE NOT (b.name = 'abc');
+----------+
| count(*) |
+----------+
|   500401 |
+----------+
1 row in set (0.97 sec)

对于子查询:

mysql> SELECT count(*)
    -> FROM inefficient_foreign_key_exclude_a a
    -> WHERE NOT ((a.id IN (
    ->     SELECT U1.`a_id` FROM `inefficient_foreign_key_exclude_b` U1 WHERE (U1.`name` = 'abc'  AND U1.`a_id` IS NOT NULL)
    -> ) AND a.id IS NOT NULL));
+----------+
| count(*) |
+----------+
|   500401 |
+----------+
1 row in set (3.76 sec)

加入快了近4倍。

1 个答案:

答案 0 :(得分:0)

看起来这是一种优化。

虽然filter()可以是'任意'条件,但它会进行连接并应用限制。

exclude()限制性更强,因此您不必强制加入表,它可以使用子查询构建查询,我认为这会使查询更快(由于索引使用)。

如果您使用的是MySQL,可以在查询中使用explain命令,看看我的建议是否正确。