SQLプロンプトデータベースチュートリアル:定数としてのスカラーユーザー定義関数の誤用

SQLプロンプトは、実用的なSQL構文プロンプトツールです。SQLプロンプトは、データベースのオブジェクト名、文法、およびコードフラグメントに従って自動的に検索し、ユーザーに適切なコード選択を提供します。自動スクリプト設定により、コードがシンプルで読みやすくなります。特に、開発者がスクリプトに慣れていない場合に便利です。SQLプロンプトがインストールされ、すぐに使用できるため、コーディング効率が大幅に向上します。さらに、ユーザーは必要に応じてカスタマイズして、期待どおりに機能させることができます。

SQLプロンプトの公式バージョンをダウンロードするにはクリックしてください

グローバルデータベース定数としてのスカラーUDFの誤った使用は、パフォーマンスの大きな問題であり、SQLプロンプトが本番コードでこのエラーを検出した場合は、必ず調査する必要があります。計算列または制約でこれらのグローバル定数を使用する必要がない限り、通常、インラインテーブル値関数に値を格納するか、ビューを使用する方が安全で便利です。

開発者は、データベースにグローバル値を設定して、Piの値などの定数、または税率、言語、ファイルURN、URLなどの変数を提供できることを期待する傾向があります。ユーザー定義のスカラー関数は単一の値を返すため、理想的な方法を提供しているようです。これは、実行頻度の低い関数や比較的小さなデータセットの処理には最適ですが、それ以外の場合は、クエリのパフォーマンスに深刻な問題が発生する可能性があります。この問題が発生するのは、SQL Serverが、スキーマで検証されていないスカラー関数が正確で決定論的であるとは考えていないため、実行時に最も安全な(最も遅いとはいえ)オプションを選択するためです。

BEGIN ... ENDブロックを使用してSQLServer関数を呼び出すと、オーバーヘッドが発生します。これは、SQL Serverがスキーマバインディングを使用して関数を作成して出力を検証できない限り、データをフィルタリングする前に各行の関数を再実行するためです。、明らかに毎回同じ値を返しても。これは比較的隠れた問題です。拡張イベントセッションでは実際に何が起こったのかが明らかになりますが、実行プランでは実際には完全な意味を示していません。

つまり、関数がスキーマにバインドされていない限り、JOIN条件、WHERE検索条件、またはSELECTリストでスカラーユーザー定義関数(UDF)を使用しないでください。SQLプロンプトは、静的コード分析ルールPE017を実装します。これは、この問題の検出と修正に役立つように特別に設計されています。スキーマバインディングとそのデータベース変更への影響に自信がない限り、変数への転送値を使用するか、ビューやインラインテーブル値関数などのモジュールを使用することをお勧めします。

問題を解く

SQLプロンプトがコードでPE017を検出した場合はどうすればよいですか?

可能なすべてのオプションを設定し、いくつかのパフォーマンステストを実行し、いくつかの推奨事項を作成します。

パターンに準拠するUDF
は、スキーマバインディングを追加することにより、システム検証後にスカラー関数を正しく使用できるようにします。リスト1は、同じ単純なWordcount関数の2つのバージョンを作成します。最初はパターンバインディングなしで、次にパターンバインディングありです。どちらのバージョンも単に定数を返します。いずれの場合も、各オブジェクトのIsDeterministic、IsPrecise、およびIsSystemVerified属性値を確認します。

最後に、3番目のバージョンを作成しました。これは、パラメーター値のみを返し、これがSQLServer検証プロセスの要因であるかどうかを確認するためにのみ使用されます。
Object_Id( 'dbo.Wordcount')がNULLでない場合DROP FUNCTION dbo.Wordcount
GO

CREATE FUNCTION dbo.Wordcount()
/ **
要約:>定数を返すスキーマバインディングのない
単純なスカラーマルチステートメント関数作成者:PhilFactor日付:2018年1月2戻り値:>整数値5 ** / RETURNS INT AS BEGIN RETURN 5 END GO











/ * SQLServerがそれを信頼するかどうかを確認するためにテストします* /
SELECT ObjectPropertyEx(
Object_Id( 'dbo.Wordcount')、N'IsDeterministic ')AS deterministic;

–正確な数値が返されますか?式の解決に浮動小数点演算が使用される場合は常に
、データ型の格納方法の性質上、結果は正確ではありません。
SELECT ObjectPropertyEx(Object_Id( 'dbo.Wordcount')、N'IsPrecise ')AS正確;

– SQL Serverは、関数が正確で決定論的であることを確認できますか?
SELECT ObjectPropertyEx(
Object_Id( 'dbo.Wordcount')、N'IsSystemVerified ')AS検証済み;
GO

Object_Id( 'dbo.WordcountSchemaBound')がNULLでない場合ドロップ関数dbo.WordcountSchemaBound
GO

CREATE FUNCTION dbo.WordcountSchemaBound()
/ **
要約:>定数を返すスキーマバインディングを
備えた単純なスカラーマルチステートメント関数の2番目のバージョン作成者:PhilFactor日付:2018年1月2戻り値:>整数値5 ** / RETURNS INT WITH SCHEMABINDING ASは、BEGIN RETURN 5 END GOを












/ *テストを繰り返して、SQLServerがdbo.WordcountSchemaBoundを信頼するかどうかを確認します* /
SELECT ObjectPropertyEx(
Object_Id( 'dbo.WordcountSchemaBound')、N'IsDeterministic ')AS deterministic;

SELECT ObjectPropertyEx(
Object_Id( 'dbo.WordcountSchemaBound')、N'IsPrecise ')AS正確;

SELECT ObjectPropertyEx(
Object_Id( 'dbo.WordcountSchemaBound')、N'IsSystemVerified ')AS検証済み;
GO

Object_Id( 'dbo.Wordcounter')がNULLでない場合DROP FUNCTION dbo.Wordcounter
GO

CREATE FUNCTION dbo.Wordcounter
/ **
要約:>パラメーター返すだけのスキーマバインディングのない
単純なスカラーマルチステートメント関数の3番目のバージョン
パラメーター
の不在が決定
要因であるかどうかをテストするには–ここにパラメーターを追加すると問題
作成者:PhilFactor
戻り値:>
渡された整数値
** /
(@ howMany INT)
RETURNS INT
AS
BEGIN
RETURN @howMany
END
GO

/ *テストを繰り返して、SQLServerがdbo.Wordcounterを信頼するかどうかを確認します* /
SELECT ObjectPropertyEx(
Object_Id( 'dbo.Wordcounter')、N'IsDeterministic ')AS deterministic;

SELECT ObjectPropertyEx(Object_Id( 'dbo.Wordcounter')、N'IsPrecise ')AS正確;

SELECT ObjectPropertyEx(
Object_Id( 'dbo.Wordcounter')、N'IsSystemVerified ')AS verifyed;
GO
リスト1リスト1
を実行すると、関数の2番目のバージョンであるWordCountSchemaBoundが表示され、これら3つのプロパティに対してtrueが返されます。これがこれらの関数を呼び出すすべてのクエリのパフォーマンスにどの程度影響するかは後で説明します。

パターンバインディングには多くの利点がありますが、この場合、定数を変数として扱うことが明示的に禁止されることを意味します。これは悪いことではありません。「定数」関数(つまり、制約またはテーブルの計算列で使用した関数)を変更すると、複雑になることがわかります。また、データベースの動作中に定数を変更しようとすると、実行中の関数を使用する計画により、関数にモード安定性ロックが設定され、モード変更が必要になるため、定数の値を変更できなくなります。 。ロック。

スカラーUDFの代替

リスト2は、スキーマにバインドする必要がない、またはスキーマにバインドされることを望まずにデータベース全体の値を格納できるスカラーUDFのいくつかの代替案を示しています。最初はビューで、次にテーブル値関数です。
IF Object_Id( 'dbo.WordCountView')IS NOT NULL DROP VIEW dbo.WordCountView
GO
CREATE VIEW dbo.WordCountView
AS
/ **
要約:>
1つの列を持つ単一の行を返す非常に単純なビュー
作成者:PhilFactor
日付:01/02 / 2018
戻り値:>
wordcount」という列を持つ単一の行
/
SELECT 5 AS wordcount
GO
IF Object_Id( 'dbo.WordCountTVF')IS NOT NULL DROP FUNCTION dbo.WordCountTVF
GO
CREATE FUNCTION dbo.WordCountTVF()
/

要約:>
単一を返すテーブル値関数
'
wordcount 'という列の行作成者:PhilFactor
日付:01/02/2018
戻り値:>
'wordcount'という列の単一行
** /
RETURNS TABLE
AS
RETURN
(SELECT 5 AS wordcount)
GO
リスト2
ビュー定義参照されるオブジェクトは、ビュー定義を無効にしたり、SQLServerにビューのインデックスを再作成させたりするような方法で変更することはできません。

追加のCHECK制約保護にもかかわらず、定数を格納するためにテーブルを使用しませんでした。テーブルのデザインは静的ではありません!ネタバレとして、ビューと同じように機能することをお伝えします。

性能試験

すべての候補ソリューションをランク付けした後、それらがどのように機能するかを見てみましょう。各オプションが英語で一般的に使用される5文字の単語をどれだけ早く見つけることができるかをテストします。これらのテストでは、すべての一般的な単語の単一の列(主キー)を持つ単純なCommonwordsテーブルを作成する必要があります。それを埋めるには、commonwordsファイルをダウンロードしてから、リスト3を実行し、正しいファイルパスを使用する必要があります。
DECLARE @AllCommonWords XML =
(SELECT * FROM OpenRowset(BULK'C:\ MyPath \ commonwords.XML '、
SINGLE_BLOB)AS x);

IF Object_Id( 'commonwords'、 'U')IS NOT NULL DROP TABLE commonwords;

CREATE TABLE commonwords(word VARCHAR(40)NOT NULL PRIMARY KEY);

INSERT INTO commonwords(word)
SELECT word = word.value( '@ el'、 'varchar(40)')
FROM @ AllCommonWords.nodes( '/ commonwords / row')AS CommonWords(word);
リスト3
タイミングについては、I記事「SQLプロンプトスニペットを使用してT-SQLの実行時間を記録する方法」で説明されている簡単なテストツールを使用します。
–タイミングを保持する一時テーブル変数を作成します
DECLARE @log TABLE

TheOrder INT IDENTITY(1、1)、
WhatHappened VARCHAR(200)、
WhenItDid DATETIME2 DEFAULT GetDate()

----タイミングの開始
INSERTINTO @log(WhatHappened)SELECT 'テスト実行の開始' –開始時に配置

–最初に、ベンチマークとして、リテラル番号を使用した場合の速度を確認します
。SELECTCount(*)FROM commonWords WHERE Len(word)= 5
INSERT INTO @log(WhatHappened)SELECT 'リテラル番号を使用した単純なクエリ' –先頭に配置

–次に、「定数」をローカル変数に転送する場合の所要時間を確認します
。DECLARE
@NumberOfLetters INT = dbo.wordcount()SELECT Count(*)FROM commonWords WHERE Len(word)= @ NumberOfLetters
INSERT INTO @log(WhatHappened) SELECT '同じクエリですが、値がUDFから変数に転送されます'

–これで、値を返すだけのスカラーUDFの長さがわかります
SELECT Count(*)FROM commonWords WHERE Len(word)= dbo.wordcounter(5)
iNSERT INTO @log(WhatHappened)SELECT '同じですが、スカラー関数を使用しますパラメータ付き '

–そして今度はスカラーUDF関数をグローバル定数として使用します
SELECT Count(*)FROM commonWords WHERE Len(word)= dbo.wordcount()
iNSERT INTO @log(WhatHappened)SELECT '「定数」と同じUDスカラー関数 '

–そして、スキーマにバインドされたスカラーUDFをグローバル定数として使用する
SELECT Count()FROM commonWords WHERE Len(word)= dbo.wordcountSchemaBound()
iNSERT INTO @log(WhatHappened)SELECT 'スキーマにバインドされたものと同じ' '定数 '' UDスカラー関数 '
–ビューを使用して同じことを実行します
SELECT Count(
)FROM commonWords
INNER JOIN dbo.WordCountView
ON Len(word)= wordcount
iNSERT INTO @log(WhatHappened)SELECT'ビューの使用内部結合を持つ定数を含む '

–クロス結合のあるビューを使用して同じことを行います
SELECT Count(*)FROM commonWords
CROSS JOIN dbo.WordCountView
where Len(word)= wordcount
iNSERT INTO @log(WhatHappened)SELECT'Using''constant ''クロス結合のあるビュー '

–そして今ではインラインテーブル値関数を使用しています。一部の機能はOKです!
SELECT Count(*)FROM commonWords
INNER JOIN dbo.WordCountTVF()
ON Len(word)= wordcount
iNSERT INTO @log(WhatHappened)SELECT 'インラインTVFを使用して定数を提供する'

-異なる構文が違いを生むかどうかを確認しますSELECTCount
)FROM commonWords
cross JOIN dbo.WordCountTVF()
WHERE Len(word)= wordcount
iNSERT INTO @log(WhatHappened)SELECT 'インラインTVFとクロス結合を使用して定数 '
SELECTending.whathappened AS test、DateDiff(ms、starting.whenItDid、ending.WhenItDid)[Time in ms] FROM @log start
INNER JOIN @logending ONending.theorder = startup.TheOrder + 1 –
すべてのリストタイミング
GO
/
これでテストセクションは終了です* /
リスト4
このコマンドを実行すると、クエリで使用されるすべての形式の定数が同じ結果を生成することを確認します。問題が何であるか、そしてそれがどれほど深刻であるかについて、時代は非常に明確です

PE017の恐ろしさを強調する図はほとんど必要ありません-constUDFの誤った使用。SQL Serverは、アーキテクチャによって制限されていない未検証のスカラーUDFを非常に慎重に実行し(各行は「まだ5を返しますか?」と尋ねます)、速度は50分の1になります。

モードに制約のないスカラーUDFの使用を回避することに加えて、このテストでは、クエリから一定の値を取得する他のメソッド間で、平均してパフォーマンスに実際の違いがないことも示されています。いずれの場合も、クエリ実行プランは同じです。

これで、分解パーツを使用して終了し、すべてをテストデータベースにきちんと配置します。
IF OBJECT_ID( 'dbo.Wordcount')はNULLではありません
DROP機能dbo.Wordcount
GO
IF OBJECT_ID( 'dbo.WordcountSchemaBound')NOT NULL IS
DROP機能dbo.WordcountSchemaBound
GO
IF OBJECT_ID( 'dbo.Wordcounter')はNOT NULL IS
DROP機能は、 dbo.Wordcounter
GO
IF OBJECT_ID( 'dbo.WordCountView')はNULLではありません
DROPビューdbo.WordCountView
GO
IF OBJECT_ID( 'dbo.WordCountTVF')NOT NULL IS
DROP機能dbo.WordCountTVF
GO
リスト5つの
推薦された提案

未検証のスカラー関数を使用すると、パラメーターがあるかどうかに関係なくすべての行でクエリが実行されるため、クエリは非常に遅くなります。

スカラーUDFをグローバル定数として使用する多くの継承されたコードに直面している場合は、スキーマバインディングを使用してそれらをやり直すことができます。ただし、これらがグローバル変数であり、リアルタイムシステムでほとんど変更されない場合は、このオプションを考えません。制約または計算列関数で使用するすべてのテーブルを一時的に変更しないと、スキーマバインディングを変更できないためです。それらを削除し、関数を変更してから、制約と計算列を置き換えます。

ビューまたはTVFはより用途が広いので、「グローバル」値を保存するためにそれらを使用することを好みます。これらが変更された場合、DDLの変更が必要になるため、変更が記録されます。唯一の問題は、制約または計算列でのみスカラー関数を使用できることです。テーブルを使用する場合、それは素晴らしいことですが、定数の変更はDDLの変更ではないことに注意してください。したがって、アクセス許可を設定して、税率などの変更許可を拒否する必要があります。

SQLプロンプトチュートリアル>>>

おすすめ

転載: blog.csdn.net/RoffeyYang/article/details/112920103