クラスター化された主キーであるCHAR(36)
"id"列を持つテーブルがあります。 INT
である「imdbId」列を含む、他の複数の列があります。
を使用して更新するとき
UPDATE Movies SET imdbId=12 WHERE id = N'012e1892-872e-4bb0-804b-f70ff59689ae';
クエリは永遠にかかります。そして、その理由はよくわかりません。 SQL Serverは(特殊文字を含まないため)Unicode文字列を変換してからインデックスを使用できませんか?
N
接頭辞を削除すると、クエリは即座に返されます。
もちろん問題は、既存のアプリケーションがN
プレフィックスを使用してすべてのクエリを実行することです。 「id」列をNCHAR
に変換していますか?
クラスター化されたPKとしてGUIDを使用していることは十分に悪いことですが、文字列にして、大文字と小文字を区別しない、ロケールに対応した比較を行うことにより、さらに悪化させる必要がありました。すごい!
さて、N
なしの文字列とN
付きの文字列を渡すことの間に大きな違いがある場合、それは愚かなCHAR(36)
列の照合順序がSQL Server照合順序(_SQL_
_)、おそらく_SQL_Latin1_General_CP1_CI_AS
_(USインストールのデフォルト)。
しかし、あなたは幸運です。クエリ自体を変更できないことを考えると、非常に役立つ3つのオプションがあります。
理想的には、id
列のデータ型をUNIQUEIDENTIFIER
に変更します。これは、_N'xxx-xx-xx...
_値を最初にGUIDに変換してからバイナリ比較を行うため(これにより Data Type Precedence )、ただし、各行は22バイト小さくなります(VARCHAR(36)
の36バイトからUNIQUEIDENTIFIER
の16バイトまで、マイナス)可変長の列を取り除くための別の2かそこら)。
#1を実行できない場合は、id
列の照合順序をWindowsの照合順序に変更し、できれば__BIN2
_などのバイナリの照合順序(_Latin1_General_100_BIN2
_で終わる)に変更します。これはスペースを節約しませんが、着信NVARCHAR
リテラルをCHAR(36)
(またはその逆)に変換するのにそれほど費用がかかりません。また、バイナリ比較になります(大文字と小文字を区別しないよりはるかに高速です)。ロケール対応)はGUIDであり、言語ルールの対象ではないため。
#2を実行することさえできない場合:id
列の照合順序を、バイナリではないWindows照合順序、おそらく_Latin1_General_100_CI_AS
_に変更します。これはスペースを節約せず、バイナリ比較も行いませんが、大文字と小文字が混在した値を使用したずさんなコーディング/アドホッククエリが可能であり、SQL Server照合で使用される変換よりもはるかに高速です。
この動作は、次のテストで確認できます。
_SET NOCOUNT ON;
CREATE TABLE dbo.GuidPkAsUI (
ID UNIQUEIDENTIFIER NOT NULL CONSTRAINT [PK_GuidPkAsUI] PRIMARY KEY,
InsertTime DATETIME NOT NULL CONSTRAINT [DF_GuidPkAsUI_InsertTime] DEFAULT (GETDATE())
);
CREATE TABLE dbo.GuidPkAsVCci (
ID CHAR(36) COLLATE Latin1_General_100_CI_AS NOT NULL CONSTRAINT [PK_GuidPkAsCHARci] PRIMARY KEY,
InsertTime DATETIME NOT NULL CONSTRAINT [DF_GuidPkAsCHARci_InsertTime] DEFAULT (GETDATE())
);
CREATE TABLE dbo.GuidPkAsVCbin (
ID CHAR(36) COLLATE Latin1_General_100_BIN2 NOT NULL CONSTRAINT [PK_GuidPkAsCHARbin] PRIMARY KEY,
InsertTime DATETIME NOT NULL CONSTRAINT [DF_GuidPkAsCHARbin_InsertTime] DEFAULT (GETDATE())
);
CREATE TABLE dbo.GuidPkAsVCsql (
ID CHAR(36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [PK_GuidPkAsCHARsql] PRIMARY KEY,
InsertTime DATETIME NOT NULL CONSTRAINT [DF_GuidPkAsCHARsql_InsertTime] DEFAULT (GETDATE())
);
INSERT INTO dbo.GuidPkAsUI ([ID])
SELECT NEWID()
FROM master.sys.objects
CROSS APPLY master.sys.all_columns;
INSERT INTO dbo.GuidPkAsVCci ([ID], [InsertTime] )
SELECT [ID], [InsertTime]
FROM dbo.GuidPkAsUI;
INSERT INTO dbo.GuidPkAsVCbin ([ID], [InsertTime] )
SELECT [ID], [InsertTime]
FROM dbo.GuidPkAsUI;
INSERT INTO dbo.GuidPkAsVCsql ([ID], [InsertTime] )
SELECT [ID], [InsertTime]
FROM dbo.GuidPkAsUI;
SELECT * FROM dbo.GuidPkAsUI;
-- Pick a value from half-way down and paste into
-- the 4 queries below
SET STATISTICS IO, TIME ON;
SELECT [InsertTime]
FROM dbo.GuidPkAsUI
WHERE [ID] = N'998CCC99-269C-4B53-A8B6-77B8475EFEF7';
SET STATISTICS IO, TIME OFF;
-- logical reads: 3
SET STATISTICS IO, TIME ON;
SELECT [InsertTime]
FROM dbo.GuidPkAsVCci
WHERE [ID] = N'998CCC99-269C-4B53-A8B6-77B8475EFEF7';
SET STATISTICS IO, TIME OFF;
-- logical reads: 3
SET STATISTICS IO, TIME ON;
SELECT [InsertTime]
FROM dbo.GuidPkAsVCbin
WHERE [ID] = N'998CCC99-269C-4B53-A8B6-77B8475EFEF7';
SET STATISTICS IO, TIME OFF;
-- logical reads: 3
SET STATISTICS IO, TIME ON;
SELECT [InsertTime]
FROM dbo.GuidPkAsVCsql
WHERE [ID] = N'998CCC99-269C-4B53-A8B6-77B8475EFEF7';
SET STATISTICS IO, TIME OFF;
-- logical reads: 7157 (yikes!)
_
SQLサーバーはUnicode文字列(特殊文字を含まないため)を変換してからインデックスを使用できませんか?
質問のこの部分に具体的に答えるために、SQLサーバーはNCHARを暗黙的にCHARに変換しません。 SQLサーバーは2つのフィールドのデータ型のみを確認し、データの損失やエラーが発生しない変換を選択します。この場合、CHARからNCHARに変換されます。これにより、テーブル内のすべての行がCHARからNCHARに変換されて、渡されたNCHARパラメータと比較されます。
また、この特定のケースでNCHARからCHARへの変換が成功するかどうかを確認するために、パラメーターの実際の文字を調べません。
おそらく、列の暗黙的な変換を回避するために、パラメーターをvarcharとしてキャストする必要があります。
UPDATE Movies SET imdbId=12 WHERE id = cast(N'012e1892-872e-4bb0-804b-f70ff59689ae' as varchar);
このソリューションはテストしていませんが、計算列をテーブルに追加し、非クラスター化インデックスを計算列に追加することで問題を解決できる可能性があります。詳細については、このリンクを参照してください。
http://www.johnsansom.com/implicit-conversions-computed-columns/