2

同じ結果セットを返す2つのクエリがありますが、どちらが最適なステートメントであるか、それとも重要ではありませんか?

SELECT A.id, B.somefield FROM (
   SELECT id from table1
   UNION
   SELECT id from table2
) A LEFT JOIN table3 B on A.id = B.id

また

SELECT A.id, B.somefield FROM table1 A LEFT JOIN table3 B on A.id = B.id
UNION
SELECT A.id, B.somefield FROM table2 B LEFT JOIN table3 B on A.id = B.id

私はそれらをデータでいっぱいにしていくつかのテストを実行できることに気づきましたが、より速いのであれば「なぜ」に興味がありますか?(私はpostgresqlを使用しています。影響がある場合に備えて)。

ありがとう。

4

5 に答える 5

3

最初に使用するための実行計画UNIONは、はるかに少ないステップを示しています。残念ながら、実行計画はすべてではありません。テーブルスキャン、論理読み取り、CPU 使用率もあるため、これがすべてではなく、すべてを終了し、データとデータに大きく依存します。あなたの指標。

重複を使用すると、最初のクエリのパフォーマンスが向上するはずです。これはUNION、結合による重複の削除が結合の前に行われるため、テーブル 3 でのテーブル スキャンの回数が少なくなるからです。テーブル 1 とテーブル 2 に重複がない場合、違いはありません。

これは、いくつかのサンプル データで実証できます。私のサンプルはすべて次の 5 つのテーブルを使用します (T4 と T5 は単に出力をダンプするためのものなので、実行計画を確認するために SQL フィドルでページを何マイルも下にスクロールする必要はありません)。

CREATE TABLE T1 (ID INT NOT NULL);
CREATE TABLE T2 (ID INT NOT NULL);
CREATE TABLE T3 (FK INT NOT NULL, SomeValue VARCHAR(10) NOT NULL);
CREATE TABLE T4 (ID INT NOT NULL, SomeValue VARCHAR(10) NULL);
CREATE TABLE T5 (ID INT NOT NULL, SomeValue VARCHAR(10) NULL);

そして、すべて以下を使用してテストします (クエリ プランのキャッシュを排除するために逆方向にも実行されます)。

INSERT INTO T4
SELECT  ID, SomeValue
FROM    T1
        LEFT JOIN T3
            ON ID = FK
UNION 
SELECT  ID, SomeValue
FROM    T2
        LEFT JOIN T3
            ON ID = FK;

INSERT INTO T5
SELECT  ID, SomeValue
FROM    (   SELECT  ID
            FROM    T1
            UNION
            SELECT  ID
            FROM    T2
        ) T
        LEFT JOIN T3
            ON ID = FK;

例 1 - T1 には、T2 にもある行が含まれています

INSERT INTO T1 (ID)
SELECT  *
FROM    GENERATE_SERIES(0, 40000);

INSERT INTO T2 (ID)
SELECT  *
FROM    GENERATE_SERIES(20000, 60000);

INSERT INTO T3 (FK, SomeValue)
SELECT  *, 'VALUE'
FROM    GENERATE_SERIES(10000, 50000);

SQL Fiddle の例は、T4 ( UNIONbefore JOIN) への挿入のパフォーマンスが向上することを示しています。私はこれを 25 回実行し、T4 への挿入を 22 回実行しました。方程式からサーバーの負荷を取り除くのに十分なデータがないため、いくつかの異常があることは予想通りです。この例では挿入の順序が逆になっていますが、やはり同様の結果が見られました。

例 2 - table1 と table2 に重複はありません

INSERT INTO T1 (ID)
SELECT  *
FROM    GENERATE_SERIES(0, 30000);

INSERT INTO T2 (ID)
SELECT  *
FROM    GENERATE_SERIES(30001, 60000);

INSERT INTO T3 (FK, SomeValue)
SELECT  *, 'VALUE'
FROM    GENERATE_SERIES(10000, 50000);

この例では、実行時間は互いに非常に接近しており、どちらのメソッドがより高速に実行されるかが頻繁に切り替わります。

サンプルデータ

サンプルデータ 2

最後に、すでに述べた点を繰り返しますが、重複を予期していない場合/重複を気にしない場合はUNION ALLパフォーマンスが向上しますが、重複がない場合は両方の方法でパフォーマンスがほぼ同じになるはずなので、両方の方法が改善されるはずです等量で。私はこれをテストしていませんが、これを確認するために使用したテスト データを変更するのは大規模な作業ではありません。

編集

SQL Fiddle でクエリを試してみたところ、ローカル マシンで行った場合よりもはるかに多くの分散が示されました。したがって、これらの例を参考にして、自分のサーバーでテストを行ってください。フェアを作成する方がはるかに簡単です。テスト環境!

于 2012-06-18T22:31:34.230 に答える
2

OK、最初idに、選択リストではあいまいです。欲しいですA.idB.id

次に、idがすべてのテーブルのインデックス付きフィールドであるとすると、重複排除と結合はどちらもNlogM操作です。ここで、Nは「左側」の行数、Mは「右側」の行数です。Nの各行について、Mの一致する行が見つかるか、見つからない必要があります(結合すると、Mで見つかった行が結果に含まれます。結合すると、Mで見つかった行は除外されます)。これは、左側のカーディナリティを最小化すると、最高のパフォーマンスが得られることを意味します。

したがって、どちらのクエリの複雑さも、テーブル1とテーブル2の間にある共有IDの数に大きく依存します。共通性がゼロ(同じ行IDがない)で、テーブルごとに100行の場合、最初のクエリは1つの100log100ユニオンを実行します。 200log100結合、および2番目のクエリは2つの100log100結合を実行し、次に100log100ユニオンを実行します。これは同等の時間で実行されます。ただし、100%の共通性(テーブル1のすべての行も2にある)では、最初のクエリは100log100ユニオンを実行し、次に100log100結合を実行します(1と2のUNIONはテーブル1と同等であるため)。クエリは引き続き2つの100log100結合と100log100ユニオンを実行します。ワーストケースは同じですが、クエリ1のベストケースはクエリ2の3分の2であるため、クエリ1を使用します。

ただし、コメント投稿者が言ったように、重複を予期しない場合は、UNIONALLの方が両方のクエリでパフォーマンスが向上します。UNIONALLのAとBの結果はA+Bであり、これは各セットのアクセス時間によってのみ制限されます(これは私が検討していませんでした)。重複を予期しないことにより、両方のクエリを最初のクエリの最良のパフォーマンスにカットできます。

于 2012-06-18T21:59:25.577 に答える
0

クエリごとにpostgresのEXPLAINを実行し、実行コストを確認します。

于 2012-06-18T21:20:17.217 に答える
0

各クエリに使用EXPLAIN ANALYZE SELECT A.id, ...し、結果を比較します。それらは同じかもしれません。

于 2012-06-18T21:18:16.943 に答える
0

以下のクエリは、@GarethD による 2 つとは異なるクエリ プランを生成しますが、ほぼ同じように実行します (主キーを追加した後)。

-- EXPLAIN ANALYZE
WITH ttt AS (
    SELECT COALESCE(t1.id,t2.id) AS id
        FROM    t1
        FULL OUTER JOIN t2 ON t1.id = t2.id
    )   
INSERT INTO t6 (id, somevalue)
SELECT tx.id AS id
    , t3.somevalue AS somevalue
FROM ttt tx
LEFT JOIN t3 ON tx.id = t3.fk
        ;

注: クエリ プランでは COALESCE() 関数が存在しないため、実際には演算子として扱われます (これは良いことです)。

                                                       QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Insert on t6  (cost=3761.65..5761.69 rows=40001 width=10) (actual time=1643.429..1643.429 rows=0 loops=1)
   CTE ttt
     ->  Hash Full Join  (cost=1004.80..2488.62 rows=40001 width=8) (actual time=129.768..399.898 rows=60001 loops=1)
           Hash Cond: (t1.id = t2.id)
           ->  Seq Scan on t1  (cost=0.00..557.01 rows=40001 width=4) (actual time=0.008..60.693 rows=40001 loops=1)
           ->  Hash  (cost=533.80..533.80 rows=37680 width=4) (actual time=129.737..129.737 rows=40001 loops=1)
                 Buckets: 4096  Batches: 1  Memory Usage: 938kB
                 ->  Seq Scan on t2  (cost=0.00..533.80 rows=37680 width=4) (actual time=0.016..62.236 rows=40001 loops=1)
   ->  Hash Left Join  (cost=1273.02..3273.06 rows=40001 width=10) (actual time=265.672..999.408 rows=60001 loops=1)
         Hash Cond: (tx.id = t3.fk)
         ->  CTE Scan on ttt tx  (cost=0.00..800.02 rows=40001 width=4) (actual time=129.776..603.317 rows=60001 loops=1)
         ->  Hash  (cost=597.01..597.01 rows=40001 width=10) (actual time=135.854..135.854 rows=40001 loops=1)
               Buckets: 4096  Batches: 2  Memory Usage: 627kB
               ->  Seq Scan on t3  (cost=0.00..597.01 rows=40001 width=10) (actual time=0.009..66.008 rows=40001 loops=1)
 Total runtime: 1644.480 ms

より良いチューニング (work_mem を減らし、ディスクベースの実行を強制し、random_page_cost を減らしてインデックスの使用を容易にする) により、計画はさらに良くなります。

SET work_mem = 64;
SET random_page_cost = 2.1;
SET seq_page_cost = 2;


                                                              QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 Insert on t6  (cost=4404.54..6654.58 rows=40001 width=10) (actual time=1573.465..1573.465 rows=0 loops=1)
   CTE ttt
     ->  Merge Full Join  (cost=0.00..2758.52 rows=40001 width=8) (actual time=0.048..348.906 rows=60001 loops=1)
           Merge Cond: (t1.id = t2.id)
           ->  Index Scan using t1_pkey on t1  (cost=0.00..1103.37 rows=40001 width=4) (actual time=0.022..67.840 rows=40001 loops=1)
           ->  Index Scan using t2_pkey on t2  (cost=0.00..1084.15 rows=37680 width=4) (actual time=0.018..68.583 rows=40001 loops=1)
   ->  Hash Left Join  (cost=1646.02..3896.06 rows=40001 width=10) (actual time=170.840..957.899 rows=60001 loops=1)
         Hash Cond: (tx.id = t3.fk)
         ->  CTE Scan on ttt tx  (cost=0.00..800.02 rows=40001 width=4) (actual time=0.055..544.544 rows=60001 loops=1)
         ->  Hash  (cost=794.01..794.01 rows=40001 width=10) (actual time=170.130..170.130 rows=40001 loops=1)
               Buckets: 1024  Batches: 32  Memory Usage: 42kB
               ->  Seq Scan on t3  (cost=0.00..794.01 rows=40001 width=10) (actual time=0.009..79.751 rows=40001 loops=1)
 Total runtime: 1574.108 ms
(13 rows)
于 2012-06-19T14:15:42.013 に答える