SQL Serverクエリオプティマイザーは、繰り返し計算された値を1つのCompute Scalar演算子に結合できます。これを行うかどうかは、クエリプランのコストと計算値のプロパティによって異なります。予想どおり、非決定論的な計算値に対してはこれは行われません。これには、などのいくつかの例外がありますRAND()
。また、ユーザー定義関数に対してもこれを行いません。
ユーザー定義関数の例から始めます。これは、ユーザー定義関数の優れた例です。
CREATE OR ALTER FUNCTION dbo.NULL_FUNCTION (@N BIGINT) RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
また、テーブルを作成し、それに100行を挿入します。
CREATE TABLE X_100 (N BIGINT NOT NULL);
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO X_100 WITH (TABLOCK)
SELECT n
FROM Nums WHERE n <= 100;
dbo.NULL_FUNCTION
関数がdetermisticです。次のクエリに対して何回実行されますか?
SELECT n, dbo.NULL_FUNCTION(n)
FROM X_100;
クエリプランに基づいて、これは行ごとに1回、または100回実行されます。
![クエリプラン1](https://i.stack.imgur.com/a0OF6.png)
SQL Server 2016では、sys.dm_exec_function_stats DMVが導入されました。そのDMVのスナップショットを取得して、UDFがクエリによって実行された回数を確認できます。
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('NULL_FUNCTION');
その結果は100なので、関数は100回実行されました。
別の簡単なクエリを試してみましょう:
SELECT n, dbo.NULL_FUNCTION(n), dbo.NULL_FUNCTION(n)
FROM X_100;
クエリプランは、関数が200回実行されることを示唆しています。
![クエリプラン2](https://i.stack.imgur.com/4j6aZ.png)
の結果はsys.dm_exec_function_stats
、関数が200回実行されたことを示しています。
クエリプランを使用して、計算スカラーが実行される回数を常に把握できるとは限らないことに注意してください。次の引用は、「スカラー、式、実行プランのパフォーマンスを計算する」からのものです。
これにより、Compute Scalarは他のほとんどの演算子と同じように動作するものと考えられます。行が流れると、Compute Scalarに含まれるすべての計算結果がストリームに追加されます。これは一般的には当てはまりません。名前にかかわらず、Compute Scalarは常に何も計算するわけではなく、単一のスカラー値を常に含むわけではありません(たとえば、ベクトル、エイリアス、またはブール述語の場合もあります)。多くの場合、Compute Scalarは単純に式を定義します。実際の計算は、実行プランの後半で結果が必要になるまで延期されます。
別の例を試してみましょう。次のクエリでは、UDFが1回計算されることを望みます。
WITH NULL_FUNCTION_CTE (NULL_VALUE) AS
(
SELECT DISTINCT dbo.NULL_FUNCTION(0)
)
SELECT n , cte.NULL_VALUE
FROM X_100
CROSS JOIN NULL_FUNCTION_CTE cte;
クエリプランは、1回だけ計算されることを示唆しています。
![クエリプラン](https://i.stack.imgur.com/TI7hM.png)
しかし、DMVは真実を明らかにします。計算スカラーは、結合演算子にある必要になるまで延期されます。100回評価されます。
また、オプティマイザが同じ式を何度も再計算しないようにするために何ができるかを尋ねました。コードでスカラーUDFを使用するのを避けるのが最善の方法です。これらには、この問題以外にも、メモリ許可の増大、クエリ全体の実行を強制する、MAXDOP 1
カーディナリティの見積もりが悪い、CPUの使用率が高くなるなど、多くのパフォーマンスの問題があります。UDFを使用する必要があり、そのUDFの値が定数である場合は、クエリの外部で計算してローカル変数に入れることができます。
UDFのないクエリの場合、同じ結果を返すが、まったく同じ方法で入力されない式を記述しないようにすることができます。この次の例では、一般に入手可能なAdventureworksDW2016CTP3データベースを使用していますが、実際にはどのデータベースでも使用できます。COUNT(*)
このクエリでは何回計算されますか?
SELECT OrderDateKey, COUNT(*)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
このクエリでは、ハッシュマッチ(集計)演算子を調べることでこれを把握できます。
![ハッシュマッチ](https://i.stack.imgur.com/uOoB6.png)
COUNT(*)
それぞれのユニークな値のために一度計算されますOrderDateKey
。ORDER BY
句を含めても、2回計算されることはありません。ここで実行計画を見ることができます。
ここで、まったく同じ結果を返すが別の方法で記述されたクエリを考えてみます。
SELECT OrderDateKey, SUM(1)
FROM dbo.FactResellerSales
GROUP BY OrderDateKey
ORDER BY COUNT(*) DESC;
クエリオプティマイザーはそれらを組み合わせるほどスマートではないため、追加の作業が行われます。
![ハッシュマッチ2](https://i.stack.imgur.com/WsnNA.png)