5

つまり、なぜこれを行うのですか:

select *
    from tableA
        /* Bunch of inner joins */
    where 
        /* Bunch of clauses */
    and (
        exists (
            select * 
                from tableB, tableC, tableD
                where (tableB.fieldNameA = 'foo') and
                   /* More clauses */
        ) or 
        exists (
            select * 
                from tableB, tableC, tableD
                where (tableB.fieldNameA = 'bar') and
                    /* More clauses */
        )
    )

これより約 500 倍高速に実行できますか?

select *
    from tableA
        /* Bunch of inner joins */
    where
        /* Bunch of clauses */
    and exists (
        select * 
            from tableB, tableC, tableD
            where (tableB.fieldNameA = 'foo' or tableB.fieldNameA = 'bar') and
                /* More clauses */
    )

私はコードを繰り返さないという考えが好きなので、2 番目のバージョンに統合したいと考えました。どちらも同じ結果セットを生成しますが、最初の実行は非常に高速で、使用できませ。考え?


私はクエリプランを調べてきましたが、実際にはその底がわかりませんでした. 私が気づいたことの 1 つは、因数分解された例では、クラスター化インデックス シーク[tree].[PK__tree__09746778]の実際の行数が 93,000 近くあることです。もう 1 つの例では、これほど多くの行数はありません。これがテキスト出力です。これがどれほど役立つかはわかりません。

最初の例 (デファクタリング) は以下を生成します:

|--Nested Loops(Left Semi Join, OUTER REFERENCES:([initialCategory].[inode]))
   |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[child]))
   |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[inode])=([OPUSDev2].[dbo].[tree].[parent]), RESIDUAL:([OPUSDev2].[dbo].[course].[inode]=[OPUSDev2].[dbo].[tree].[parent]))
   |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[section].[inode], [Expr1037]) WITH UNORDERED PREFETCH)
   |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[course_term].[inode], [Expr1036]) WITH UNORDERED PREFETCH)
   |    |    |    |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[course_number])=([OPUSDev2].[dbo].[course_term].[course_number]), RESIDUAL:([OPUSDev2].[dbo].[course_term].[course_number]=[OPUSDev2].[dbo].[course].[course_number]))
   |    |    |    |    |    |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course].[PK__course__31B762FC]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_web],0)=(1) AND CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_nav],0)=(1)))
   |    |    |    |    |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[term].[term_id])=([OPUSDev2].[dbo].[course_term].[term_id]))
   |    |    |    |    |         |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[term].[PK__term__0697FACD]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[term].[active_on_web],0)=(1)))
   |    |    |    |    |         |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course_term].[course_term_pk]))
   |    |    |    |    |--Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[section_idx2]), SEEK:([OPUSDev2].[dbo].[section].[course_term_inode]=[OPUSDev2].[dbo].[course_term].[inode]) ORDERED FORWARD)
   |    |    |    |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[PK__section__7B264821]), SEEK:([OPUSDev2].[dbo].[section].[inode]=[OPUSDev2].[dbo].[section].[inode]),  WHERE:([OPUSDev2].[dbo].[section].[status]='Active') LOOKUP ORDERED FORWARD)
   |    |    |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]))
   |    |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [initialCategory]), SEEK:([initialCategory].[inode]=[OPUSDev2].[dbo].[tree].[child]),  WHERE:([OPUSDev2].[dbo].[category].[active] as [initialCategory].[active]=(1)) ORDERED FORWARD)
   |--Concatenation
        |--Nested Loops(Inner Join)
        |    |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
        |    |--Nested Loops(Inner Join, OUTER REFERENCES:([parentCategory].[inode]))
        |         |--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='noncredit_subjects') ORDERED FORWARD)
        |         |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode] AND [OPUSDev2].[dbo].[tree].[parent]=[OPUSDev2].[dbo].[category].[inode] as [parentCategory].[inode]) ORDERED FORWARD)
        |--Nested Loops(Inner Join)
             |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
             |--Nested Loops(Inner Join, OUTER REFERENCES:([parentCategory].[inode]))
                  |--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='credit_subjects') ORDERED FORWARD)
                  |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode] AND [OPUSDev2].[dbo].[tree].[parent]=[OPUSDev2].[dbo].[category].[inode] as [parentCategory].[inode]) ORDERED FORWARD)

2 番目の例 (因数分解) は次のようになります。

|--Nested Loops(Left Semi Join, OUTER REFERENCES:([initialCategory].[inode]))
   |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[child]))
   |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[inode])=([OPUSDev2].[dbo].[tree].[parent]), RESIDUAL:([OPUSDev2].[dbo].[course].[inode]=[OPUSDev2].[dbo].[tree].[parent]))
   |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[section].[inode], [Expr1029]) WITH UNORDERED PREFETCH)
   |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[course_term].[inode], [Expr1028]) WITH UNORDERED PREFETCH)
   |    |    |    |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[course_number])=([OPUSDev2].[dbo].[course_term].[course_number]), RESIDUAL:([OPUSDev2].[dbo].[course_term].[course_number]=[OPUSDev2].[dbo].[course].[course_number]))
   |    |    |    |    |    |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course].[PK__course__31B762FC]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_web],0)=(1) AND CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_nav],0)=(1)))
   |    |    |    |    |    |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[term].[term_id])=([OPUSDev2].[dbo].[course_term].[term_id]))
   |    |    |    |    |         |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[term].[PK__term__0697FACD]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[term].[active_on_web],0)=(1)))
   |    |    |    |    |         |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course_term].[course_term_pk]))
   |    |    |    |    |--Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[section_idx2]), SEEK:([OPUSDev2].[dbo].[section].[course_term_inode]=[OPUSDev2].[dbo].[course_term].[inode]) ORDERED FORWARD)
   |    |    |    |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[PK__section__7B264821]), SEEK:([OPUSDev2].[dbo].[section].[inode]=[OPUSDev2].[dbo].[section].[inode]),  WHERE:([OPUSDev2].[dbo].[section].[status]='Active') LOOKUP ORDERED FORWARD)
   |    |    |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]))
   |    |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [initialCategory]), SEEK:([initialCategory].[inode]=[OPUSDev2].[dbo].[tree].[child]),  WHERE:([OPUSDev2].[dbo].[category].[active] as [initialCategory].[active]=(1)) ORDERED FORWARD)
   |--Nested Loops(Inner Join)
        |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
        |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[parent]))
             |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
             |--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='credit_subjects' AND [parentCategory].[inode]=[OPUSDev2].[dbo].[tree].[parent] OR [parentCategory].[category_key]='noncredit_subjects' AND [parentCategory].[inode]=[OPUSDev2].[dbo].[tree].[parent]) ORDERED FORWARD)
4

1 に答える 1

5

速度の違いがある理由を確実に知る唯一の方法は、各クエリのクエリ プランを表示し、オプティマイザの違いを確認することです。経験的に、OR句をUNION句に変更すると、オプティマイザーがインデックスをより適切に使用できるようになり、クエリの実行時間が短縮されることがわかっています。

ここの記事では、選択性を含むクエリ プランに影響を与えるいくつかの変数について、非常に適切な説明が提供されています。

UNION がスキャンではなくより多くのシークを引き起こす理由は、シークの資格を得るために、各操作が特定の選択性要件を満たす必要があるためです。(選択性とは、フィルタリングされる特定の列の一意性です)。OR は 1 回の操作で発生するため、各列の選択性が組み合わされて特定の割合を超えると、スキャンがより効率的であると見なされます。

あなたは、次のブロックがすべての違いを生むと考えているようです:

where (table.fieldNameA = 'foo' or table.fieldNameA = 'bar')

デフォルトでは、UNION はステートメントごとに個別の操作を実行するため、各列の選択性は結合されず、シークを実行する可能性が高くなります。UNION は 2 つの操作を実行するため、上記の連結操作を使用して結果セットを一致させる必要があります。通常、これは高価な操作ではありません。

ここで言及しUNIONている理由は、最適化された SQL の動作がUNION.

exists (
            select * 
                from tableB, tableC, tableD
                where (table.fieldNameA = 'foo') and
                   /* More clauses */
        ) or 
        exists (
            select * 
                from tableB, tableC, tableD
                where (table.fieldNameA = 'bar') and
                    /* More clauses */
        )

2 つの別個のサブクエリがあり、それぞれを並列化して、完了時に結合することができます。これらの各サブクエリは、より優れた選択率も備えているため、スキャンではなくシークの使用が推奨されます。

これは、(更新された) クエリ プランによってサポートされます。最初の計画では、下部に次のように表示されます。

Concatenation
        |--Nested Loops(Inner Join)
        |    |--Clustered Index Seek
        |    |--Nested Loops
        |         |--Index Seek
        |         |--Clustered Index Seek
        |--Nested Loops(Inner Join)
             |--Clustered Index Seek
             |--Nested Loops
                  |--Index Seek
                  |--Clustered Index Seek

OR条件が並列化できる 2 つの別個のジョブに分割されていることを示しています。選択性が向上するため、インデックス シークのパフォーマンスが向上する可能性が非常に高くなります。

クエリのパフォーマンスが低い場合は常に、クエリ プランを表示して、どのサブセクションに非常に時間がかかっているかを確認し、最初にそれらを修正する必要があります。プログラムのパフォーマンスを最適化する方法とよく似ています。

テーブル内の行数、使用可能なインデックス、特定の列の選択性など、非常に多くの変数がクエリ プランに影響を与える可能性があるため、クエリを目視で最適化することは非常に困難です。

于 2012-12-04T21:50:30.410 に答える