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


10

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 level120 で構成されています。

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

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)クエリに追加しても違いはありません。


あなたが試すことができるselect r.id, LEFT(remark, 512)かもしれません(またはどんなサブセンスの長さでもいいかもしれません)。
mustaccio

@フォレスト:問題のシミュレーションに必要なデータを再現しようとしています。一見すると、ネストされたループの推定値が低いことに関係しています。私のダミーデータでは、推定行数ははるかに多く、流出は発生していません
Frederik Vanderhaegen

回答:


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による優れた分析を読みたい場合は、こちらをお読みください。


7

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

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

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

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

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

統計が役に立たないので、あなたの最善の策はおそらくクエリの書き換えです- フォレスト彼の答えでカバーしました。


3

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

私はいくつかのテストデータを作成し、最後に2つのソリューションを考え出し、IDフィールドをresources変数(常に一意の場合)または一時テーブル(複数の場合がある場合)に格納しました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

予想通り、良い見積もり。

では、より良い見積もりを得るために何を変更できるでしょうか?

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%正しい見積もり

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.