web-dev-qa-db-ja.com

SQL Serverオプティマイザが外部キーを使用できないのはなぜですか

次の表があります(完全な再現可能なSQLについては以下を参照してください)。

ToBeTransferredTemp (ID varchar(100),type varchar(100) NOT NULL, other...)
CODES(Key1,Key2,Key3, IDSTRING varchar(100), other...)

次のクエリがあります。

SELECT *
FROM
ToBeTransferredTemp a 
INNER JOIN codes c on a.type = c.idstring

ToBeTransferredTemp.typeにインデックスがあり、CODES.idstringに一意のインデックスがあります。 ToBeTransferredTemp.type-> code.idstringには外部キー制約があります

SQL Serverはこのことから収集すると思います。結合によって、ToBeTransferredTempにあるのと同じ数の行が生成されます。しかし、そうではありません。 ToBeTransferredTempには8500行あり、結合後の推定行数は450行です。「実際の行数」は、当然のことながら8500行です。

SQL Serverがひどく失敗する理由は何ですか?外部キー制約がSQL Serverによって信頼されていることを確認しました。

SQL Server 2017では、結合のカーディナリティを推定するときに外部キーと一意の制約が考慮されないのでしょうか?

LEFT OUTER JOINを実行するだけでこれを簡単に修正できることはわかっていますが、とても不器用に感じます。 SQL Serverがこれを理解できない理由を知りたいです。

EDIT:queryplan: https://www.brentozar.com/pastetheplan/?id=ByL54PtNB

編集:SQLを再現する必要があります。クエリを数回再実行する必要がある場合があることに注意してください。時々、値はそれが機能しているように見える統計を形成します

CREATE TABLE dbo.TEST_CODES
(
    key1 varchar(60) NOT NULL,
    key2 varchar(60) NOT NULL,
    key3 datetime NOT NULL,
    Filler4 varchar(100),
    IDSTRING varchar(100)
);
ALTER TABLE [dbo].[TEST_CODES] ADD  CONSTRAINT [PK_CODES_TEST] PRIMARY KEY CLUSTERED 
(
    key1 ASC,
    key2 ASC,
    key3 ASC
)

CREATE UNIQUE INDEX UQ_Codes_IDSTRING ON dbo.TEST_Codes (IDSTRING);

CREATE TABLE dbo.TEST_ToBeTransferredTemp 
(
    ID varchar(100) NOT NULL,
    [type] varchar(100) NOT NULL,
    Filler1 varchar(100) NOT NULL,
    Filler2 varchar(50) NOT NULL,
    Filler3 int NULL,

    CONSTRAINT PK_ToBeTransferredTemp_TEST PRIMARY KEY (ID),
    CONSTRAINT FK_ToBeTransferredTemp_type FOREIGN KEY ([type]) 
        REFERENCES dbo.TEST_CODES (IDSTRING)
);

CREATE NONCLUSTERED INDEX IX_type ON dbo.TEST_ToBeTransferredTemp ([type]);

INSERT INTO dbo.TEST_CODES
SELECT TOP (18139) 
    CAST(NEWID() AS varchar(60)),
    CAST(NEWID() AS varchar(60)),
    GETDATE(),
    CAST(NEWID() AS varchar(100)),
    CAST(NEWID() AS varchar(100))
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2;

INSERT INTO dbo.TEST_ToBeTransferredTemp
SELECT TOP (8002)
    CAST(NEWID() AS varchar(100)),
    (SELECT TOP 1 IDSTRING FROM TEST_CODES order by newid()),
    CAST(NEWID() AS varchar(100)),
    CAST(NEWID() AS varchar(50)),
    NULL
     FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2;

SELECT *
FROM
TEST_ToBeTransferredTemp a 
inner join TEST_codes c on a.type = c.idstring
3
Henrik Alstad

問題を再現できません。つまり、説明に何か欠落している可能性があります(または、説明に欠落している可能性があります!)。この問題の詳細なヘルプが必要な場合は、この再現コードに変更を提案して、状況にさらに一致させることができます。

以下は、説明に基づいてテーブルとデータを再作成する試みです。

ToBeTransferredTemp.typeにインデックスがあります
およびCODES.idstringの一意のインデックス。
ToBeTransferredTemp.type-> code.idstringに外部キー制約があります

これら3つの情報に加えて、2つのテーブルのそれぞれにクラスター化インデックスがあることに気付きました。

USE [master];
GO
DROP DATABASE IF EXISTS [245776];
CREATE DATABASE [245776];
GO

USE [245776];
GO

CREATE TABLE dbo.CODES
(
    IDSTRING varchar(100),
    Filler1 varchar(100) NOT NULL,
    Filler2 varchar(50) NOT NULL,
    Filler3 int NULL,

    CONSTRAINT PK_CODES PRIMARY KEY (IDSTRING)
);

CREATE UNIQUE INDEX UQ_Codes_IDSTRING ON dbo.Codes (IDSTRING);

CREATE TABLE dbo.ToBeTransferredTemp 
(
    ID varchar(100),
    [type] varchar(100) NOT NULL,
    Filler1 varchar(100) NOT NULL,
    Filler2 varchar(50) NOT NULL,
    Filler3 int NULL,

    CONSTRAINT PK_ToBeTransferredTemp PRIMARY KEY (ID),
    CONSTRAINT FK_ToBeTransferredTemp_type FOREIGN KEY ([type]) 
        REFERENCES dbo.CODES (IDSTRING)
);

CREATE NONCLUSTERED INDEX IX_type ON dbo.ToBeTransferredTemp ([type]);

以下は、クラスター化された主キー、外部キー制約、NCインデックス、および一意のインデックスを持つ2つのテーブルです。

元のキールックアッププランの形状を可能にするために、いくつかのフィラー列を含めました。

INSERT INTO dbo.CODES
SELECT TOP (8500) 
    CAST(NEWID() AS varchar(100)),
    CAST(NEWID() AS varchar(100)),
    CAST(NEWID() AS varchar(50)),
    v1.number
FROM master.dbo.spt_values v1
    CROSS JOIN master.dbo.spt_values v2;

INSERT INTO dbo.ToBeTransferredTemp
SELECT 
    CAST(NEWID() AS varchar(100)),
    c.IDSTRING,
    CAST(NEWID() AS varchar(100)),
    CAST(NEWID() AS varchar(50)),
    NULL
FROM dbo.CODES c;

これにより、各テーブルに正確に8500行がロードされます。

OPに記述されているクエリは、マージ結合プランを生成します。

SELECT *
FROM ToBeTransferredTemp a 
    INNER JOIN codes c
        ON a.[type] = c.idstring;

screenshot of merge join plan in SSMS

OPからプランの形状を取得するには、結合タイプ(INNER LOOP JOIN)とCODESテーブルに一意のインデックスでアクセスする必要があることを示唆します。

SELECT *
FROM ToBeTransferredTemp a 
    INNER LOOP JOIN codes c WITH (INDEX (UQ_Codes_IDSTRING)) 
        ON a.[type] = c.idstring;

screenshot of loop join plan in SSMS

これは正しいプランの形のようです。ただし、 この計画では見積もりは正確です

2
Josh Darnell