2

我试图弄清楚为什么 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?

更新

我做了一个测试来证明使用子查询根本没有效率。a我在和b表中创建了 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 倍。

4

1 回答 1

0

看起来这是一种优化。

尽管filter()可以是“任何”条件,但它会进行连接并应用限制。

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

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

于 2013-06-11T09:36:50.553 回答