興味がありました。ご存知のように、好奇心は猫を殺すという評判があります。
では、猫の皮を剥ぐのに最も速い方法はどれですか?
このテストのキャット スキン環境:
- 適切な RAM と設定を備えた Debian Squeeze 上のPostgreSQL 9.0 。
- 6,000 人の学生、24,000 のクラブ会員 (データは、実際のデータを含む同様のデータベースからコピーされたものです。)
- 質問の命名スキーマからのわずかな転用:
student.id
is student.stud_id
and club.id
is club.club_id
here.
- このスレッドでは、作成者にちなんでクエリに名前を付けました。
- すべてのクエリを数回実行してキャッシュにデータを入力し、
EXPLAIN ANALYZE
.
- 関連するインデックス (どのクラブが照会されるかを事前に把握していない限り、最適である必要があります):
ALTER TABLE student ADD CONSTRAINT student_pkey PRIMARY KEY(stud_id );
ALTER TABLE student_club ADD CONSTRAINT sc_pkey PRIMARY KEY(stud_id, club_id);
ALTER TABLE club ADD CONSTRAINT club_pkey PRIMARY KEY(club_id );
CREATE INDEX sc_club_id_idx ON student_club (club_id);
club_pkey
ここでのほとんどのクエリでは必要ありません。
主キーは、PostgreSQL で一意のインデックスを自動的に実装します。最後のインデックスは、PostgreSQLのマルチカラム インデックス
の既知の欠点を補うものです。
複数列 B ツリー インデックスは、インデックスの列の任意のサブセットを含むクエリ条件で使用できますが、インデックスは先頭 (一番左) の列に制約がある場合に最も効率的です。
結果
からの総実行時間EXPLAIN ANALYZE
。
1) マーティン 2: 44.594 ミリ秒
SELECT s.stud_id, s.name
FROM student s
JOIN student_club sc USING (stud_id)
WHERE sc.club_id IN (30, 50)
GROUP BY 1,2
HAVING COUNT(*) > 1;
2) アーウィン 1: 33.217 ミリ秒
SELECT s.stud_id, s.name
FROM student s
JOIN (
SELECT stud_id
FROM student_club
WHERE club_id IN (30, 50)
GROUP BY 1
HAVING COUNT(*) > 1
) sc USING (stud_id);
3) マーティン 1: 31.735 ミリ秒
SELECT s.stud_id, s.name
FROM student s
WHERE student_id IN (
SELECT student_id
FROM student_club
WHERE club_id = 30
INTERSECT
SELECT stud_id
FROM student_club
WHERE club_id = 50
);
4) デレク: 2.287 ミリ秒
SELECT s.stud_id, s.name
FROM student s
WHERE s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 30)
AND s.stud_id IN (SELECT stud_id FROM student_club WHERE club_id = 50);
5) アーウィン 2: 2.181 ミリ秒
SELECT s.stud_id, s.name
FROM student s
WHERE EXISTS (SELECT * FROM student_club
WHERE stud_id = s.stud_id AND club_id = 30)
AND EXISTS (SELECT * FROM student_club
WHERE stud_id = s.stud_id AND club_id = 50);
6) ショーン: 2.043 ミリ秒
SELECT s.stud_id, s.name
FROM student s
JOIN student_club x ON s.stud_id = x.stud_id
JOIN student_club y ON s.stud_id = y.stud_id
WHERE x.club_id = 30
AND y.club_id = 50;
最後の 3 つのパフォーマンスはほとんど同じです。4) と 5) は同じクエリ プランになります。
後期追加
ファンシー SQL ですが、パフォーマンスが追いつきません:
7) ypercube 1: 148.649 ミリ秒
SELECT s.stud_id, s.name
FROM student AS s
WHERE NOT EXISTS (
SELECT *
FROM club AS c
WHERE c.club_id IN (30, 50)
AND NOT EXISTS (
SELECT *
FROM student_club AS sc
WHERE sc.stud_id = s.stud_id
AND sc.club_id = c.club_id
)
);
8) ypercube 2: 147.497 ミリ秒
SELECT s.stud_id, s.name
FROM student AS s
WHERE NOT EXISTS (
SELECT *
FROM (
SELECT 30 AS club_id
UNION ALL
SELECT 50
) AS c
WHERE NOT EXISTS (
SELECT *
FROM student_club AS sc
WHERE sc.stud_id = s.stud_id
AND sc.club_id = c.club_id
)
);
予想通り、この 2 つのパフォーマンスはほぼ同じです。クエリ プランの結果はテーブル スキャンになり、プランナはここでインデックスを使用する方法を見つけられません。
9) ワイルドプラッサー 1: 49.849 ミリ秒
WITH RECURSIVE two AS (
SELECT 1::int AS level
, stud_id
FROM student_club sc1
WHERE sc1.club_id = 30
UNION
SELECT two.level + 1 AS level
, sc2.stud_id
FROM student_club sc2
JOIN two USING (stud_id)
WHERE sc2.club_id = 50
AND two.level = 1
)
SELECT s.stud_id, s.student
FROM student s
JOIN two USING (studid)
WHERE two.level > 1;
ファンシー SQL、CTE のまともなパフォーマンス。非常にエキゾチックなクエリ プラン。
10) ワイルドプラッサー 2: 36.986 ミリ秒
WITH sc AS (
SELECT stud_id
FROM student_club
WHERE club_id IN (30,50)
GROUP BY stud_id
HAVING COUNT(*) > 1
)
SELECT s.*
FROM student s
JOIN sc USING (stud_id);
クエリの CTE バリアント 2)。驚くべきことに、まったく同じデータを使用しても、クエリ プランがわずかに異なる可能性があります。student
サブクエリ バリアントがインデックスを使用している でシーケンシャル スキャンを見つけました。
11) ypercube 3: 101.482 ミリ秒
もう1つの後期追加ypercube。方法がいくつもあるというのは、確かに驚くべきことです。
SELECT s.stud_id, s.student
FROM student s
JOIN student_club sc USING (stud_id)
WHERE sc.club_id = 10 -- member in 1st club ...
AND NOT EXISTS (
SELECT *
FROM (SELECT 14 AS club_id) AS c -- can't be excluded for missing the 2nd
WHERE NOT EXISTS (
SELECT *
FROM student_club AS d
WHERE d.stud_id = sc.stud_id
AND d.club_id = c.club_id
)
);
12) アーウィン 3: 2.377 ミリ秒
ypercube の 11) は、実際には、この単純なバリアントの頭をひねる逆のアプローチであり、それもまだ欠けていました。上位の猫とほぼ同じ速度で実行します。
SELECT s.*
FROM student s
JOIN student_club x USING (stud_id)
WHERE sc.club_id = 10 -- member in 1st club ...
AND EXISTS ( -- ... and membership in 2nd exists
SELECT *
FROM student_club AS y
WHERE y.stud_id = s.stud_id
AND y.club_id = 14
);
13) アーウィン 4: 2.375 ミリ秒
信じられないかもしれませんが、これはまったく新しい亜種です。2つ以上のメンバーシップの可能性があると思いますが、2つだけでトップの猫にもランクされています.
SELECT s.*
FROM student AS s
WHERE EXISTS (
SELECT *
FROM student_club AS x
JOIN student_club AS y USING (stud_id)
WHERE x.stud_id = s.stud_id
AND x.club_id = 14
AND y.club_id = 10
);
動的なクラブ会員数
言い換えれば、さまざまな数のフィルターです。この質問では、正確に2 つのクラブ メンバーシップを求めています。しかし、多くのユースケースでは、さまざまな数に備える必要があります。見る: