カーディナリティの見積もりをSQL Serverオプティマイザー(任意のバージョン)に「注入」する方法はありますか?
つまり、Oracleのカーディナリティのヒントに似たものです。
私の動機は、記事 クエリオプティマイザーは本当に良いですか?[1]、そこで彼らはカーディナリティ推定量が悪い計画の選択に及ぼす影響をテストします。したがって、SQL Serverに複雑なクエリのカーディナリティを正確に「推定」させることができれば十分です。
[1] Leis、Viktor、他「クエリオプティマイザーは本当にどれほど優れているのですか?」
VLDB Endowment 9.3(2015)の議事録:204-215。
CARDINALITY
とMANY()
と呼ばれるユーザー定義関数を戦略的に使用して、OracleのTOP
ヒントに似たものを取得できます Adam Machanicによって開発 。いくつかの例を見てみましょう。無料で利用できるAdventureWorksデータベースを使用しています。次のクエリで、th
派生テーブルによって返される行数を本当に制御する必要があると仮定します。
_SELECT
p.Name
, th.ProductId
, th.Quantity
, th.ActualCost
FROM Production.Product p
INNER JOIN (
SELECT ProductId, Quantity, ActualCost
FROM Production.TransactionHistory
) th ON p.ProductID = th.ProductID;
_
現状のままで、113443行の見積もりを取得します。
th
から見積もりを下げる必要がある場合は、TOP
と_OPTIMIZE FOR
_クエリヒントを使用して行の目標を設定できます。これを行う1つの方法を次に示します。
_DECLARE @row_goal BIGINT = 9223372036854775807;
SELECT
p.Name
, th.ProductId
, th.Quantity
, th.ActualCost
FROM Production.Product p
INNER JOIN (
SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
FROM Production.TransactionHistory
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1));
_
見積もりが1行だけであることがわかります。
結果の変更を避けるために、_@row_goal
_を最大のBIGINT
値に設定しました。 _OPTIMIZE FOR
_クエリヒントは、_@row_goal
_が1に等しいかのようにオプティマイザにクエリを最適化するように指示します。同じ結果が得られますが、クエリの最適化は異なります。
カーディナリティーの見積もりを増やすと、よりトリッキーになります。オプティマイザは十分な行を返さないことを認識するため、TOP
の値を増やすことはできません。ただし、MANY()
関数を使用して、推定に行を追加できます。 MANY()
関数は常に0行を返しますが、そこからの行推定値は入力パラメーターによって変化することに注意してください。派生テーブルからの行推定値を10X増やす必要があるとします。それを達成する1つの方法:
_SELECT
p.Name
, th.ProductId
, th.Quantity
, th.ActualCost
FROM Production.Product p
INNER JOIN (
SELECT TOP (9223372036854775807) ProductId, Quantity, ActualCost
FROM Production.TransactionHistory
LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1
) th ON p.ProductID = th.ProductID;
_
推定値はベーステーブルの10倍であることがわかります。
オプティマイザがテーブルを移動しないようにするために、余分なTOP
が追加されました。これがないと、MANY()
関数がプランの間違った場所に適用される可能性があります。
行数に係数を掛けるのではなく、正確な過大評価が必要な場合は、2つの手法を組み合わせることができます。たとえば、派生テーブルの推定値が正確に1000000行であることが本当に必要だとします。それを達成する1つの方法:
_DECLARE @row_goal BIGINT = 9223372036854775807;
SELECT
p.Name
, th.ProductId
, th.Quantity
, th.ActualCost
FROM Production.Product p
INNER JOIN (
SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
FROM Production.TransactionHistory
LEFT OUTER JOIN dbo.Many(10) AS m ON
1=1
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1000000));
_
推定値が1000000行であることがわかります。
これらは、クエリの最適化に必要とされないことが多い高度な手法であることを注意する必要があります。詳細については、Adam Machanicによる Clash of the Row Goals をご覧になることをお勧めします。
_-- By Adam Machanic, reproduced with permission
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'Many' AND OBJECT_SCHEMA_NAME(object_id) = 'dbo')
DROP FUNCTION dbo.Many
GO
CREATE FUNCTION dbo.Many(@n INT)
RETURNS TABLE AS
RETURN
(
WITH
a(x) AS
(
SELECT
*
FROM
(
VALUES
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)
) AS x0(x)
)
SELECT TOP(@n)
1 AS x
FROM
a AS a1,
a AS a2
WHERE
a1.x % 2 = 0
)
GO
_
カーディナリティの見積もりをオプティマイザに直接注入する方法はありませんが、達成したいことに応じて、いくつかのオプションがあります。
OPTION (FAST N)
クエリヒントを使用して行の目標を導入し、CTEまたはサブクエリを使用してクエリを書き直して、実行計画のさまざまな部分に_TOP...ORDER BY
_ベースの行の目標を挿入できますが、私はより複雑な構造を試してみたときに、結果のクエリがどれほど効率的かはわかりません。
より完全な説明については オプティマイザ内部:行目標の詳細 を参照してください。
オプティマイザが選択する演算子に影響を与えたい場合は、カーディナリティの見積もりを注入する必要はありませんが、OPTION (MERGE JOIN)
やOPTION (HASH JOIN)
などを使用して、物理結合演算子を強制できます。 。
この記事では、ヒントを使用してプランに影響を与える方法について詳しく説明します。 ヒントを使用して実行プランを制御する
計画を修正したい場合は、計画ガイドを使用することもできます。
繰り返しになりますが、実際のユースケースが何であるかは明確ではありません。これらの手法を使用すると、走行距離が異なる場合があります。多くの場合、オプティマイザに決定を任せて、オプティマイザが十分な情報に基づいて決定できるように、最新の統計があることを確認することをお勧めします。
関連するMicrosoft Connectの提案: クエリでフィルター選択性のヒントを指定できるようにする xor88。マイクロソフトは答えた:
フィードバックをお寄せいただきありがとうございます。これの潜在的な利点を見ることができます。一般的に、私たちは自動動作をできるだけ良くし、この種のヒントの必要性を回避するように努力しますが、もちろん他にも多くのヒントがあります。これは将来のリリースで検討する予定ですが、Denali(11.0)バージョンを超える予定です。
宜しくお願いします、
エリック・ハンソン
プログラムマネージャー
SQL Serverクエリ処理
SQL Serverを使用できますOPTIMIZE FOR
コンパイル時に実際の値(パラメーター)または不明な値(変数)を使用する代わりに、ヒントされた値に基づいてカーディナリティの推定を強制するクエリヒント。詳細については、SQL Serverドキュメントの クエリヒントトピック を参照してください。
たとえば、以下のクエリは、ローカル変数の場合とは異なり、全体的な平均カーディナリティではなく、ヒント値からの統計ヒストグラムに基づいて行数を推定します。
DECLARE
@StartDate datetime = '20150101'
, @EndDate datetime = '20150102';
SELECT *
FROM dbo.Example
WHERE
DateColumn BETWEEN @StartDate AND @EndDate
OPTION(OPTIMIZE FOR(@StartDate = '20100101', @EndDate='20100101'));
同様に、ヒントをパラメーターに使用して、コンパイル中に実際のパラメーター値ではなくヒント値からの統計ヒストグラムに基づいて推定を行うことができます。
DECLARE
@StartDate datetime = '20150101'
, @EndDate datetime = '20150102';
EXECUTE sp_executesql N'SELECT *
FROM dbo.Example
WHERE
DateColumn BETWEEN @StartDate AND @EndDate
OPTION(OPTIMIZE FOR(@StartDate = ''20100101'', @EndDate=''20100101''));'
, N'@StartDate datetime, @EndDate datetime'
, @StartDate = @StartDate
, @EndDate = @EndDate;
ヒントのリテラルの代わりにUNKNOWN
キーワードを指定して、実際のパラメーター値と統計ヒストグラムに基づいて推定するのではなく、全体の平均カーディナリティーを使用できます。