web-dev-qa-db-ja.com

varchar(max)が原因で流出をtempdbにソート

32 GBのサーバーでは、最大メモリが25 GBのSQL Server 2014 SP2を実行しており、2つのテーブルがあります。ここでは、両方のテーブルの構造が簡略化されています。

_CREATE TABLE [dbo].[Settings](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceId] [int] NULL,
    [typeID] [int] NULL,
    [remark] [varchar](max) NULL,
    CONSTRAINT [PK_Settings] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Resources](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceUID] [int] NULL,
 CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO
_

次の非クラスター化インデックス:

_CREATE NONCLUSTERED INDEX [IX_UID] ON [dbo].[Resources]
(
    [resourceUID] ASC
)

CREATE NONCLUSTERED INDEX [IX_Test] ON [dbo].[Settings]
(
    [resourceId] ASC,
    [typeID] ASC
)
_

データベースは_compatibility level_ 120で構成されています。

これを実行すると querytempdbへの流出があります。これは私がクエリを実行する方法です:

_exec sp_executesql N'
select r.id,remark
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38
_

_[remark]_フィールドを選択しない場合、スピルは発生しません。私の最初の反応は、ネストされたループオペレーターの推定行数が少ないため、スピルが発生したというものでした。

そのため、5つの日時列と5つの整数列を設定テーブルに追加し、それらを選択ステートメントに追加します。クエリを実行しても、流出は発生していません。

流出が発生するのは、なぜ_[remark]_が選択されている場合のみですか?これはおそらくvarchar(max)であるという事実と関係があります。 tempdbへの流出を防ぐにはどうすればよいですか?

OPTION (RECOMPILE)をクエリに追加しても違いはありません。

10

ここではいくつかの可能な回避策があります。

手動で調整する メモリを許可することはできますが、おそらく私はそのルートをたどらないでしょう。

CTEとTOPを使用して、最大長の列を取得する前に、ソートを低くすることもできます。以下のようになります。

WITH CTE AS (
SELECT TOP 1000000000 r.ID, s.ID AS ID2, s.typeID
FROM Resources r
inner join Settings s on resourceid=r.id
where resourceUID=@UID
ORDER BY s.typeID
)
SELECT c.ID, ca.remark
FROM CTE c
CROSS APPLY (SELECT remark FROM dbo.Settings s WHERE s.id = c.ID2) ca(remark)
ORDER BY c.typeID

概念実証dbfiddle ここ 。サンプルデータはまだありがたいです!

Paul Whiteによる優れた分析を読みたい場合は、 こちらをご覧ください

10
Forrest

[備考]を選択した場合にのみ、流出が発生するのはなぜですか?

その列を含めると、ソートされている大きな文字列データに対して十分なメモリ許可が得られないため、流出が発生しています。

実際の行数は推定行数(実際の行数1,302対推定行数126)よりも10倍多いため、十分なメモリが付与されません。

見積もりがずれているのはなぜですか? SQL Serverは、dbo.Settingsにresourceidが38の行が1つしかないとなぜ思いますか?

これは統計情報の問題である可能性があり、DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')を実行して確認し、そのヒストグラムステップのカウントを確認できます。しかし、実行計画は、統計が可能な限り完全で最新であることを示しているようです。

統計が役に立たないので、あなたの最善の策はおそらくクエリの書き換えです-これは Forrestの回答でカバーしています。

7
Josh Darnell

私には、クエリのwhere句が問題を引き起こしているように見え、OPTION(RECOMPILE)が使用されている場合でも、推定が低い原因となっています。

私はいくつかのテストデータを作成し、最後に2つのソリューションを思い付きました。IDresourcesからのフィールドを変数(常に一意の場合)または一時テーブル(次の場合)に格納しましたIDは複数持つことができます。

ベーステストレコード

_SET NOCOUNT ON
DECLARE @i int= 1;
WHILE @i <= 10000
BEGIN
INSERT INTO [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(@i,@i,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here'); -- 23254 character length on each value
INSERT INTO  [dbo].[Resources](resourceUID)
VALUES(@i);
SET @i += 1;
END
_

OPと同じ概算結果セット(1300レコード)を取得するために、「シーク」値を挿入します

_INSERT INTO  [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(38,38,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here')
GO 1300
_

互換性を変更し、OPと一致するように統計を更新します

_ALTER DATABASE StackOverflow SET COMPATIBILITY_LEVEL = 120;
UPDATE STATISTICS settings WITH FULLSCAN;
UPDATE STATISTICS resources WITH FULLSCAN;
_

元のクエリ

_exec sp_executesql N'
select r.id
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38
_

私の推定は偶数です 悪い 、1つの推定行で、1300が返されます。そして、OPが述べたように、OPTION(RECOMPILE)を追加しても問題ありません

注意すべき重要な点は、where句を取り除くと、推定値が100%正確になることです。これは、両方のテーブルのすべてのデータを使用しているためです。

ポイントを証明するために、前のクエリと同じものを使用することを確認するために、インデックスを強制しました

_exec sp_executesql N'
select r.id,remark
FROM Resources r with(index([IX_UID]))
inner join Settings WITH(INDEX([IX_Test])) 
on resourceid=r.id
ORDER BY typeID',
N'@UID int',
@UID=38
_

予想通り、 good 見積もり。

それでは、より良い見積もりを得るために何を変更し、それでも値を求めることができますか?

OPの例のように、@ UIDが一意の場合、idから返された単一のresourcesを変数に入れ、OPTION(RECOMPILE)を使用してその変数を検索できます。

_DECLARE @UID int =38 , @RID int;
SELECT @RID=r.id from 
Resources r where resourceUID = @UID;

SELECT @uid, remark 
from Settings 
where resourceId = @uid 
Order by typeID
OPTION(RECOMPILE);
_

100%正確になります 推定

しかし、リソースに複数のresourceUIDがある場合はどうなりますか?

いくつかのテストデータを追加します

_INSERT INTO Resources(ResourceUID)
VALUES (38);
go 50
_

これは一時テーブルで解決できます

_CREATE TABLE #RID (id int)
DECLARE @UID int =38 
INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID
_

ここでも、正確な 推定 を使用します。

これは私自身のデータセット、YMMVで行われました。


sp_executesqlで記述

変数付き

_exec sp_executesql N'
DECLARE  @RID int;
    SELECT @RID=r.id from 
    Resources r where resourceUID = @UID;

    SELECT @uid, remark 
    from Settings 
    where resourceId = @uid 
    Order by typeID
    OPTION(RECOMPILE);',
N'@UID int',
@UID=38
_

一時テーブルあり

_exec sp_executesql N'

CREATE TABLE #RID (id int)

INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID',
N'@UID int',
@UID=38
_

それでも私のテストでは100%正しい見積もりです

3
Randi Vertongen