スカラー値関数を使用すると、連続して使用される回数が増えるほど、クエリの実行速度が累積的に遅くなるように見えるのはなぜですか?
サードパーティから購入したデータを使用して作成されたこのテーブルがあります。
この投稿を短くするためにいくつかの要素を削除しました...しかし、設定方法がわかるようにしています。
CREATE TABLE [dbo].[GIS_Location](
[ID] [int] IDENTITY(1,1) NOT NULL, --PK
[Lat] [int] NOT NULL,
[Lon] [int] NOT NULL,
[Postal_Code] [varchar](7) NOT NULL,
[State] [char](2) NOT NULL,
[City] [varchar](30) NOT NULL,
[Country] [char](3) NOT NULL,
CREATE TABLE [dbo].[Address_Location](
[ID] [int] IDENTITY(1,1) NOT NULL, --PK
[Address_Type_ID] [int] NULL,
[Location] [varchar](100) NOT NULL,
[State] [char](2) NOT NULL,
[City] [varchar](30) NOT NULL,
[Postal_Code] [varchar](10) NOT NULL,
[Postal_Extension] [varchar](10) NULL,
[Country_Code] [varchar](10) NULL,
次に、LATとLONを検索する2つの関数があります。
CREATE FUNCTION [dbo].[usf_GIS_GET_LAT]
(
@City VARCHAR(30),
@State CHAR(2)
)
RETURNS INT
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE @LAT INT
SET @LAT = (SELECT TOP 1 LAT FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)
RETURN @LAT
END
CREATE FUNCTION [dbo].[usf_GIS_GET_LON]
(
@City VARCHAR(30),
@State CHAR(2)
)
RETURNS INT
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE @LON INT
SET @LON = (SELECT TOP 1 LON FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)
RETURN @LON
END
私が以下を実行すると...
SET STATISTICS TIME ON
SELECT
dbo.usf_GIS_GET_LAT(City,[State]) AS Lat,
dbo.usf_GIS_GET_LON(City,[State]) AS Lon
FROM
Address_Location WITH(NOLOCK)
WHERE
ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)
SET STATISTICS TIME OFF
100〜 = 8ミリ秒、200〜 = 32ミリ秒、400〜 = 876ミリ秒
-編集申し訳ありませんが、もっと明確にすべきでした。上記のクエリを調整するつもりはありません。これは、クランチするレコードが増えるほど実行時間が遅くなることを示すサンプルにすぎません。実世界のアプリケーションでは、関数はwhere句の一部として使用され、都市と州の周囲に半径を作成して、その地域のすべてのレコードを含めます。
ほとんどの場合、テーブルを参照するスカラー値関数は避けるのが最善です。テーブルは基本的にブラックボックスであり、行ごとに1回実行する必要があり、クエリプランエンジンで最適化できないためです。したがって、関連付けられたテーブルにインデックスがある場合でも、線形にスケーリングする傾向があります。
インラインテーブル値関数はクエリとインラインで評価され、最適化できるため、インラインテーブル値関数の使用を検討することをお勧めします。必要なカプセル化を取得しますが、selectステートメントに式を直接貼り付けるパフォーマンス。
インライン化の副作用として、手続き型コードを含めることはできません(@variableを宣言しない; @variable = ..; returnを設定する)。ただし、複数の行と列を返すことができます。
関数を次のように書き直すことができます。
create function usf_GIS_GET_LAT(
@City varchar (30),
@State char (2)
)
returns table
as return (
select top 1 lat
from GIS_Location with (nolock)
where [State] = @State
and [City] = @City
);
GO
create function usf_GIS_GET_LON (
@City varchar (30),
@State char (2)
)
returns table
as return (
select top 1 LON
from GIS_Location with (nolock)
where [State] = @State
and [City] = @City
);
それらを使用するための構文も少し異なります。
select
Lat.Lat,
Lon.Lon
from
Address_Location with (nolock)
cross apply dbo.usf_GIS_GET_LAT(City,[State]) AS Lat
cross apply dbo.usf_GIS_GET_LON(City,[State]) AS Lon
WHERE
ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)
そうではありません。
スカラー関数の実行される行数に応じてパフォーマンスが指数関数的に低下するスカラー関数のバグはありません。テストを再試行し、SQLプロファイラー、CPU、READS、およびDURATIONの列を確認してください。テストサイズを増やして、1秒、2秒、5秒より長くかかるテストを含めます。
CREATE FUNCTION dbo.slow
(
@ignore int
)
RETURNS INT
AS
BEGIN
DECLARE @slow INT
SET @slow = (select count(*) from sysobjects a
cross join sysobjects b
cross join sysobjects c
cross join sysobjects d
cross join sysobjects e
cross join sysobjects f
where a.id = @ignore)
RETURN @slow
END
go
SET STATISTICS TIME ON
select top 1 dbo.slow(id)
from sysobjects
go
select top 5 dbo.slow(id)
from sysobjects
go
select top 10 dbo.slow(id)
from sysobjects
go
select top 20 dbo.slow(id)
from sysobjects
go
select top 40 dbo.slow(id)
from sysobjects
SET STATISTICS TIME OFF
出力
SQL Server Execution Times:
CPU time = 203 ms, elapsed time = 202 ms.
SQL Server Execution Times:
CPU time = 889 ms, elapsed time = 939 ms.
SQL Server Execution Times:
CPU time = 1748 ms, elapsed time = 1855 ms.
SQL Server Execution Times:
CPU time = 3541 ms, elapsed time = 3696 ms.
SQL Server Execution Times:
CPU time = 7207 ms, elapsed time = 7392 ms.
結果セットの行に対してスカラー関数を実行している場合、スカラー関数はグローバル最適化なしで行ごとに実行されることに注意してください。
機能をインラインTVFでラップできます。これにより、はるかに高速になります。
結果セットのすべての行に対して、関数を2回(DBへの2回の選択ヒット)呼び出します。
クエリを高速化するには、GIS_Locationに直接結合し、関数をスキップします。
SELECT
g.Lat,
g.Lon
FROM
Address_Location l WITH(NOLOCK)
INNER JOIN GIS_Location g WITH(NOLOCK) WHERE l.State = g.State AND l.City = g.City
WHERE
ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)
なぜNOLOCK、またはクレイジーなwhere句を、質問からコピーしたのかわかりません...
簡単に言うと、ユーザー定義関数を含むSQL式は、関数を含まないSQL式よりも効率が低いためです。実行ロジックを最適化することはできません。また、関数のオーバーヘッド(プロトコルの呼び出しを含む)は、すべての行で発生する必要があります。
KMikeのアドバイスは良いです。 WHERE .. IN(SELECT何か)は効率的なパターンではない可能性が高く、この場合、JOINに簡単に置き換えることができます。
これがうまく機能するかどうかを確認してください...または、別の内部結合ですか?
select a.*,
(select top 1 g.Lat from GIS_Location g where g.City = a.City and g.State = a.State) as Lat,
(select top 1 g.Lon from GIS_Location g where g.City = a.City and g.State = a.State) as Lon
from Address_Location a
where a.ID in (select top 100 ID from Address_Location order by ID desc)
スカラー関数のパフォーマンスについては、よくわかりません。
このパーティーに遅れて参加して申し訳ありませんが、将来のプロファイラーの犠牲者のために私の答えを共有したいと思います。数日前、1つの本番サーバー(SQL Server 2012 sp4エンタープライズ)のすべてのスカラー関数が遅くなり、通常は完了するのに数秒かかる一部のストアドプロシージャが、1つのケースでは数分、数時間で実行され始めました。
最後に、プロファイラーで作成されたトレースがこれの根本的な原因でした。トレースが開始されましたが、このトレースが実行されていたラップトップの電源がオフになり、事前にトレースを停止しませんでした。奇跡のように、トレースはユーザーsaによって自動的に停止されました(記録のために、saアカウントは無効にされ、名前が変更されました)-"SQLトレースが停止しました。トレースID = '3'。ログイン名= 'sa'。"これにより、パフォーマンスの問題が自動的に解決されます。
したがって、低速サーバーで実行されているプロファイラートレースまたは拡張イベントを確認してください
これが将来誰かに役立つことを願っています。
通常、スカラー関数は、インラインTVF関数よりもはるかに低速です。幸いなことに、多くのシナリオで変更されます。
SQL Server 2019ではスカラーUDFインライン化が導入されます:
機能のインテリジェントクエリ処理スイートの下にある機能。 この機能により、SQL ServerでスカラーUDFを呼び出すクエリのパフォーマンスが向上します(SQL Server 2019プレビュー以降)
T-SQLScalarユーザー定義関数
Transact-SQLに実装され、単一のデータ値を返すユーザー定義関数は、T-SQLスカラーユーザー定義関数と呼ばれます。 T-SQL UDFは、SQLクエリ全体でコードの再利用とモジュール性を実現するための洗練された方法です。一部の計算(複雑なビジネスルールなど)は、命令型UDF形式で表現する方が簡単です。 UDFは、複雑なSQLクエリの記述に関する専門知識を必要とせずに、複雑なロジックを構築するのに役立ちます。
スカラーUDFは通常、次の理由によりパフォーマンスが低下します。
- 反復呼び出し
- 原価計算の欠如
- 解釈された実行
- シリアル実行
スカラーUDFの自動インライン化
スカラーUDFインライン化機能の目標は、UDFの実行が主なボトルネックであるT-SQLスカラーUDFを呼び出すクエリのパフォーマンスを向上させることです。
この新機能により、スカラーUDFは自動的にスカラー式またはスカラーサブクエリに変換され、UDF演算子の代わりに呼び出しクエリで置き換えられます。次に、これらの式とサブクエリが最適化されます。 その結果、クエリプランにはユーザー定義の関数演算子がなくなりますが、その効果はビューやインラインTVFなどのプランで観察されます。
インライン化可能なスカラーUDFの要件
次のすべての条件が当てはまる場合、スカラーT-SQLUDFをインラインにすることができます。
UDFは、次の構成を使用して記述されています。
- DECLARE、SET:変数の宣言と割り当て。
- SELECT:単一/複数の変数が割り当てられたSQLクエリ1。
- IF/ELSE:任意のレベルのネストによる分岐。
- RETURN:単一または複数のreturnステートメント。
- UDF:ネストされた/再帰的な関数呼び出し2。
- その他:EXISTS、ISNULLなどの関係演算。
UDFは、時間に依存する(GETDATE()など)または副作用3(NEWSEQUENTIALID()など)の組み込み関数を呼び出しません。
- UDFは、EXECUTE AS CALLER句を使用します(EXECUTE AS句が指定されていない場合のデフォルトの動作)。
- UDFは、テーブル変数またはテーブル値パラメーターを参照しません。
- スカラーUDFを呼び出すクエリは、GROUPBY句でスカラーUDF呼び出しを参照しません。
- UDFはネイティブにコンパイルされていません(相互運用がサポートされています)。
- UDFは、計算列またはチェック制約定義では使用されません。
- UDFはユーザー定義の型を参照しません。
- UDFに追加された署名はありません。
- UDFはパーティション関数ではありません。
関数がインライン化できないかどうかの確認:
SELECT OBJECT_NAME([object_id]) AS name, is_inlineable
FROM sys.sql_modules
WHERE [object_id] = OBJECT_ID('schema.function_name')
データベースレベルでの機能の有効化/無効化:
ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON/OFF;