sql >> データベース >  >> RDS >> Sqlserver

SQLServer2019でのスカラーUDFインライン化

    Scalar UDFは常に両刃の剣でした。クエリ全体で繰り返すのではなく、面倒なロジックを抽象化する開発者には最適ですが、オプティマイザーが実行しないため、本番環境での実行時のパフォーマンスにはひどいものです。それらをうまく処理します。基本的に、UDFの実行は残りの実行プランから分離されているため、行ごとに1回呼び出され、推定または実際の行数に基づいて最適化したり、プランの残りの部分に折りたたんだりすることはできません。

    SQL Server 2000以降の最善の努力にもかかわらず、スカラーUDFの使用を効果的に阻止することはできないので、SQLServerがそれらをより適切に処理できるようにするのは素晴らしいことではないでしょうか。

    SQL Server 2019では、ScalarUDFインライン化と呼ばれる新機能が導入されています。機能を分離する代わりに、全体的な計画に組み込まれます。これにより、実行プランが大幅に改善され、実行時のパフォーマンスが向上します。

    ただし、最初に、問題の原因をわかりやすく説明するために、SQL Server 2017(または2019で、互換性レベルは低い)で実行されているデータベースで、数行しかない単純なテーブルのペアから始めましょう。

    CREATE DATABASE Whatever;
    GO
    ALTER DATABASE Whatever SET COMPATIBILITY_LEVEL = 140;
    GO
    USE Whatever;
    GO
     
    CREATE TABLE dbo.Languages
    (
      LanguageID int PRIMARY KEY,
      Name sysname
    );
     
    CREATE TABLE dbo.Employees
    (
      EmployeeID int PRIMARY KEY,
      LanguageID int NOT NULL FOREIGN KEY REFERENCES dbo.Languages(LanguageID)
    );
     
    INSERT dbo.Languages(LanguageID, Name) VALUES(1033, N'English'), (45555, N'Klingon');
     
    INSERT dbo.Employees(EmployeeID, LanguageID)
      SELECT [object_id], CASE ABS([object_id]%2) WHEN 1 THEN 1033 ELSE 45555 END 
      FROM sys.all_objects;

    これで、各従業員とその主要言語の名前を表示する簡単なクエリができました。このクエリがさまざまな場所で、またはさまざまな方法で使用されているとしましょう。そのため、クエリに結合を構築する代わりに、その結​​合を抽象化するスカラーUDFを記述します。

    CREATE FUNCTION dbo.GetLanguage(@id int)
    RETURNS sysname
    AS
    BEGIN
      RETURN (SELECT Name FROM dbo.Languages WHERE LanguageID = @id);
    END

    次に、実際のクエリは次のようになります。

    SELECT TOP (6) EmployeeID, Language = dbo.GetLanguage(LanguageID)
      FROM dbo.Employees;

    クエリの実行プランを見ると、奇妙なことに何かが欠けています:

    言語にはアクセスできないが従業員にはアクセスできることを示す実行プラン

    言語テーブルにはどのようにアクセスしますか?この計画は、関数自体と同様に、関連する複雑さの一部を抽象化しているため、非常に効率的に見えます。実際、このグラフィカルプランは、定数または変数をLanguageに割り当てるだけのクエリと同じです。 列:

    SELECT TOP (6) EmployeeID, Language = N'Sanskrit'
      FROM dbo.Employees;

    ただし、元のクエリに対してトレースを実行すると、メインクエリに加えて、実際には関数への呼び出しが6つ(各行に1つ)あることがわかりますが、これらのプランはSQLServerによって返されません。

    sys.dm_exec_function_statsを確認してこれを確認することもできます 、ただしこれは保証ではありません

    SELECT [function] = OBJECT_NAME([object_id]), execution_count 
      FROM sys.dm_exec_function_stats
      WHERE object_name(object_id) IS NOT NULL;
    function         execution_count
    -----------      ---------------
    GetLanguage                    6

    SentryOne Plan Explorerは、製品内から実際の計画を生成した場合にステートメントを表示しますが、それらはトレースからのみ取得でき、個々の関数呼び出しに対して収集または表示される計画はまだありません。

    個々のスカラーUDF呼び出しのトレースステートメント

    これはすべて、トラブルシューティングを非常に困難にします。なぜなら、彼らがそこにいることをすでに知っている場合でも、彼らを追い詰める必要があるからです。また、推定コストなどに基づいて2つのプランを比較する場合、関連するオペレーターが物理的な図から隠れているだけでなく、コストもプランのどこにも組み込まれていないため、パフォーマンス分析が非常に混乱する可能性があります。

    SQLServer2019への早送り

    何年にもわたる問題のある動作とあいまいな根本原因の後で、一部の機能を全体的な実行計画に最適化できるようになりました。 Scalar UDFインライン化により、アクセスするオブジェクトがトラブルシューティングのために表示され、実行計画戦略に組み込まれるようになります。現在、カーディナリティの見積もり(統計に基づく)により、関数が行ごとに1回呼び出された場合には不可能だった結合戦略が可能になりました。

    上記と同じ例を使用して、SQL Server 2019データベースに同じオブジェクトのセットを作成するか、プランキャッシュをスクラブして互換性レベルを150に上げることができます。

    ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
    GO
    ALTER DATABASE Whatever SET COMPATIBILITY_LEVEL = 150;
    GO

    ここで、6行のクエリを再度実行すると:

    SELECT TOP (6) EmployeeID, Language = dbo.GetLanguage(LanguageID)
      FROM dbo.Employees;

    言語テーブルとそれにアクセスするためのコストを含む計画を取得します:

    スカラーUDF内で参照されるオブジェクトへのアクセスを含むプラン

    ここで、オプティマイザーはネストされたループ結合を選択しましたが、さまざまな状況下で、別の結合戦略を選択し、並列処理を検討し、基本的に自由にプランの形状を完全に変更できた可能性があります。 6行を返すクエリでこれが発生する可能性は低く、パフォーマンスの問題ではありませんが、大規模な場合は発生する可能性があります。

    この計画は、関数が行ごとに呼び出されていないことを反映しています。シークは実際には6回実行されますが、関数自体がsys.dm_exec_function_statsに表示されなくなっていることがわかります。 。取り除くことができる1つの欠点は、このDMVを使用して、関数がアクティブに使用されているかどうかを判断すると(プロシージャやインデックスでよく行うように)、信頼性が失われることです。

    警告

    すべてのスカラー関数がインライン化できるわけではなく、関数がインライン化可能である場合でも、必ずしもすべてのシナリオでインライン化されるとは限りません。これは多くの場合、関数の複雑さ、関連するクエリの複雑さ、または両方の組み合わせのいずれかに関係しています。 sys.sql_modulesで関数がインライン化可能かどうかを確認できます カタログビュー:

    SELECT OBJECT_NAME([object_id]), definition, is_inlineable
      FROM sys.sql_modules;

    また、何らかの理由で特定の関数(またはデータベース内の関数)をインライン化したくない場合は、データベースの互換性レベルに依存してその動作を制御する必要はありません。単にチャンネルを変更するのではなく、部屋を切り替えて別のテレビ番組を視聴するような、ゆるい結合が好きだったことはありません。 INLINEオプションを使用して、モジュールレベルでこれを制御できます:

    ALTER FUNCTION dbo.GetLanguage(@id int)
    RETURNS sysname
    WITH INLINE = OFF
    AS
    BEGIN
      RETURN (SELECT Name FROM dbo.Languages WHERE LanguageID = @id);
    END
    GO

    また、これはデータベースレベルで制御できますが、互換性レベルとは別にしてください。

    ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;

    そのハンマーを振るにはかなり良いユースケースが必要ですが、私見です。

    結論

    さて、ロジックのすべての部分をスカラーUDFに抽象化して、SQLServerがすべてのケースを処理することを想定しているわけではありません。スカラーUDFの使用量が多いデータベースがある場合は、最新のSQL Server 2019 CTPをダウンロードし、そこにデータベースのバックアップを復元し、DMVをチェックして、時が来たときにインライン化できる関数の数を確認する必要があります。次回のアップグレードについて議論するときは、基本的にすべてのパフォーマンスが得られ、トラブルシューティングの時間を無駄にするため、これは大きな問題になる可能性があります。

    それまでの間、スカラーUDFのパフォーマンスに問題があり、すぐにSQL Server 2019にアップグレードしない場合は、問題を軽減するのに役立つ他の方法がある可能性があります。

    注:別の記事を別の場所に投稿していることに気付く前に、この記事を書き、キューに入れました。


    1. T-SQLストアドプロシージャで動的にテーブルを作成する方法は?

    2. Salesforce.comリンクサーバーとsp_columns_ex

    3. PostgreSQLクエリでDESCを注文するときにNULL値が最初に来るのはなぜですか?

    4. MariaDBでのFROM_DAYS()のしくみ