web-dev-qa-db-ja.com

プレフィックスがNのセレクターを使用したcharのクエリは非常に遅い

クラスター化された主キーである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に変換していますか?

enter image description here

2
user3711864

クラスター化されたPKとしてGUIDを使用していることは十分に悪いことですが、文字列にして、大文字と小文字を区別しない、ロケールに対応した比較を行うことにより、さらに悪化させる必要がありました。すごい!

さて、Nなしの文字列とN付きの文字列を渡すことの間に大きな違いがある場合、それは愚かなCHAR(36)列の照合順序がSQL Server照合順序(_SQL__)、おそらく_SQL_Latin1_General_CP1_CI_AS_(USインストールのデフォルト)。

しかし、あなたは幸運です。クエリ自体を変更できないことを考えると、非常に役立つ3つのオプションがあります。

  1. 理想的には、id列のデータ型をUNIQUEIDENTIFIERに変更します。これは、_N'xxx-xx-xx..._値を最初にGUIDに変換してからバイナリ比較を行うため(これにより Data Type Precedence )、ただし、各行は22バイト小さくなります(VARCHAR(36)の36バイトからUNIQUEIDENTIFIERの16バイトまで、マイナス)可変長の列を取り除くための別の2かそこら)。

  2. #1を実行できない場合は、id列の照合順序をWindowsの照合順序に変更し、できれば__BIN2_などのバイナリの照合順序(_Latin1_General_100_BIN2_で終わる)に変更します。これはスペースを節約しませんが、着信NVARCHARリテラルをCHAR(36)(またはその逆)に変換するのにそれほど費用がかかりません。また、バイナリ比較になります(大文字と小文字を区別しないよりはるかに高速です)。ロケール対応)はGUIDであり、言語ルールの対象ではないため。

  3. #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!)
_
4
Solomon Rutzky

SQLサーバーはUnicode文字列(特殊文字を含まないため)を変換してからインデックスを使用できませんか?

質問のこの部分に具体的に答えるために、SQLサーバーはNCHARを暗黙的にCHARに変換しません。 SQLサーバーは2つのフィールドのデータ型のみを確認し、データの損失やエラーが発生しない変換を選択します。この場合、CHARからNCHARに変換されます。これにより、テーブル内のすべての行がCHARからNCHARに変換されて、渡されたNCHARパラメータと比較されます。

また、この特定のケースでNCHARからCHARへの変換が成功するかどうかを確認するために、パラメーターの実際の文字を調べません。

0
J.P.

おそらく、列の暗黙的な変換を回避するために、パラメーターをvarcharとしてキャストする必要があります。

UPDATE Movies SET imdbId=12 WHERE id = cast(N'012e1892-872e-4bb0-804b-f70ff59689ae' as varchar);
0
vercelli

このソリューションはテストしていませんが、計算列をテーブルに追加し、非クラスター化インデックスを計算列に追加することで問題を解決できる可能性があります。詳細については、このリンクを参照してください。

http://www.johnsansom.com/implicit-conversions-computed-columns/

0
Scott Hodgin