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表と表の両方に 500401 行を作成しましたb。そして、ここで私が得たもの:

参加する場合:

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)

Join はほぼ 4 倍高速です。

4

1 に答える 1

0

一種の最適化のようです。

filter()「任意」の条件にすることができますが、結合を行い、制限を適用します。

exclude()はより制限的であるため、テーブルを結合する必要はなく、サブクエリを使用してクエリを作成できます。これにより、クエリが高速になります (インデックスの使用により)。

MySQL を使用している場合はexplain、クエリでコマンドを使用して、私の提案が正しいかどうかを確認できます。

于 2013-06-11T09:36:50.553 に答える