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)
をクエリに追加しても違いはありません。
ここではいくつかの可能な回避策があります。
手動で調整する メモリを許可することはできますが、おそらく私はそのルートをたどらないでしょう。
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による優れた分析を読みたい場合は、 こちらをご覧ください
[備考]を選択した場合にのみ、流出が発生するのはなぜですか?
その列を含めると、ソートされている大きな文字列データに対して十分なメモリ許可が得られないため、流出が発生しています。
実際の行数は推定行数(実際の行数1,302対推定行数126)よりも10倍多いため、十分なメモリが付与されません。
見積もりがずれているのはなぜですか? SQL Serverは、dbo.Settingsにresourceid
が38の行が1つしかないとなぜ思いますか?
これは統計情報の問題である可能性があり、DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')
を実行して確認し、そのヒストグラムステップのカウントを確認できます。しかし、実行計画は、統計が可能な限り完全で最新であることを示しているようです。
統計が役に立たないので、あなたの最善の策はおそらくクエリの書き換えです-これは Forrest が の回答でカバーしています。
私には、クエリの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
_
予想通り、 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%正しい見積もりです