ベンダーによってサポートされているアプリケーションがあり、非常に重い論理読み取りを実行し、プロシージャ内で時間がかかるコードがあったため、クエリを少し調整してIOを減らし、データとパフォーマンスの点でテストではうまく機能しましたが、本番環境での失敗とさまざまなデータの返却により、変更をロールバックする必要がありました。これについて専門家の助言が必要です。
これはテスト環境で3か月以上データのテストが行われ、データの問題はありませんでした。展開後すぐに本番環境で障害が発生し始め、一貫性のないデータが生成されていました。
既存のクエリ:
SELECT @Ref= CAST(MAX(ISNULL(CAST(ref_clnt AS INT),0))+1 AS VARCHAR(10))
FROM table_name WITH(NOLOCK)
WHERE s_mode='value'
提案されたクエリ:
SELECT @Ref = ref_clnt+1 FROM table_name WITH(NOLOCK)
WHERE RefNo = (SELECT MAX(RefNo) FROM table_name WHERE s_mode = 'value')
テーブルのDDLは次のとおりです。
CREATE TABLE [dbo].[table_name](
[RefNo] [dbo].[udt_RefNo] NOT NULL,
[S_Mode] [varchar](10) NOT NULL,
[ref_clnt] [varchar](50) NULL)
CONSTRAINT [PK_table_name] PRIMARY KEY CLUSTERED
(
[RefNo] ASC
)
クエリで使用される定義からそれらの列のみを提供します。
Udt_RefNo
は、次のようなユーザー定義のデータ型です。
CREATE TYPE [dbo].[udt_RefNo] FROM [char](16) NOT NULL
GO
SQL Serverのバージョン:Microsoft SQL Server 2014(SP3)Copyright(c)Microsoft Corporation Enterprise Edition(64-bit)。
以下に示すように、列をカバーする非クラスター化インデックス:
CREATE NONCLUSTERED INDEX [ncidx_table_name_1] ON [dbo].[table_name]
(
[S_Mode] ASC,
[S_Status] ASC
)
INCLUDE ( [ref_clnt])
要求に応じてクエリプランを見つけてください。
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
(1 row affected)
Table 'table_name'. Scan count 1, logical reads 2732, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row affected)
SQL Server Execution Times:
CPU time = 157 ms, elapsed time = 161 ms.
(1 row affected)
Table 'table_name'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row affected)
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
ベンダーは次の提案を返しました
SELECT MAX(ISNULL(ref_clnt,0))+1 FROM table_name WITH(NOLOCK) WHERE S_Mode='value'
問題は、それらのすべてがほとんどのシナリオで機能するように見えるため、それらをテストする方法ですが、失敗するのはごくわずかな場合のみです。私はアプリケーションとそのベンダーがサポートしているものをあまり知らないので、ビジネスロジックが基本的な手順でどのように機能するかなどの詳細を取得できません。
書き換えは元のクエリと同じセマンティクスを持たないため、結果が異なっても当然のことです。
これは、NOLOCK
ヒントを気にせずに真実であり、たとえ(どういうわけか)最高の_ref_clnt
_値が最高のRefNo
値を持つ行に関連付けられていることが保証されていても。 this db <> fiddle example を参照してください。
コンピューターを扱う場合は精度が重要であるため、データ型とEdgeのケースについて慎重に検討する必要があります。最大のRefNo
計算ではstring sorting semanticsが使用されるため、 '999'は '1000'よりも上位にソートされます。クエリには、他にもいくつかの重要な違いがあります。それらすべてをリストするつもりはありませんが、NULL
の処理は 別の例 です。
また、コードの両方のバージョンに多数のバグがあります。 varchar(10)
に収まらないため、-1000000000以下の_ref_clnt
_値が返されると、元の値は失敗します。符号は長さ11になります。
安全にコードの元のバージョンを改善する最も簡単な方法は、計算された列にインデックスを追加することです。
_ALTER TABLE dbo.table_name
ADD ref_cc AS
ISNULL(CAST(ref_clnt AS integer), 0);
CREATE NONCLUSTERED INDEX i
ON dbo.table_name (S_Mode, ref_cc);
_
次に、実行プランは、指定された_ref_clnt
_値の(整数としてソートされた)最高の_S_Mode
_行を直接シークできます。
ベンダーの元のSQLは議論の余地のない品質である可能性がありますが、少なくともそれはより高速に実行され、同じ結果を生成します。
ベンダーの新しい提案:
_SELECT MAX(ISNULL(ref_clnt,0))+1 FROM table_name WITH(NOLOCK) WHERE S_Mode='value'
_
... ISNULL
は最初のパラメータのデータ型を使用するため、少なくとも理論的にはまだ問題があります。したがって、整数リテラル_0
_は暗黙的にvarchar(50)
。
check_expressionの値は、それがNULLでない場合に返されます。それ以外の場合、タイプが異なる場合、check_expressionのタイプに暗黙的に変換された後、replacement_valueが返されます。 replacement_valueは、replacement_valueがcheck_expressionよりも長い場合、切り捨てられます。
MAX
は引き続き文字列を操作するため、 予期しない結果 が生成される可能性があります。いずれの場合も、(異なる)計算された列インデックスがないと、式はまだシークできません。
_@Ref
_はvarchar(10)
だと思います。 _Ref_clnt
_はvarchar(50)
であり、それに追加しています。
_SELECT @Ref = ref_clnt+1
_
...暗黙の変換を生成します。
この問題は警告として実行プランに表示され、アプリケーションのパフォーマンスに影響を与えました。私が見たケースのほとんどは、合体ステートメントが次のように使用されたときです:
_coalesce(field_name, 0) = 0
_
あるべき時:
_coalesce(field_name, '0') = '0' or coalesce(field_name, '') = ''
_
..._field_name
_はvarchar
だったからです。