由于varchar(max),将溢出溢出排序到tempdb


10

在具有32GB的服务器上,我们正在运行SQL Server 2014 SP2,最大内存为25GB,我们有两个表,在这里您可以找到两个表的简化结构:

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个datetime和5个整数列,并将它们添加到我的select语句中。当我执行查询时,没有溢出发生。

为什么只在[remark]选中时才发生溢出?这可能与事实有关varchar(max)。我该怎么做才能避免溅到tempdb

添加OPTION (RECOMPILE)到查询没有区别。


也许您可以尝试select r.id, LEFT(remark, 512)(或任何合理的子字符串长度)。
mustaccio

@Forrest:我正在尝试重现模拟问题所需的数据。乍一看,它与嵌套循环的低估计有关。在我的虚拟数据中,估计的行数要高得多,并且不会发生溢出
Frederik Vanderhaegen,

Answers:


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

为什么仅在选择[备注]时才发生溢出?

当您包含该列时,会发生溢出,因为您没有为要排序的大字符串数据获得足够大的内存授权。

您没有获得足够大的内存授权,因为实际的行数比估计的行数多10倍(1,302实际对126估计的)。

为什么估算不正确?为什么SQL Server认为dbo.Settings中只有一行resourceid(38)?

这可能是一个统计问题,您可以通过运行检查DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')并查看该直方图步骤的计数。但是执行计划似乎表明统计数据已经尽可能完整和最新。

由于统计数据无济于事,因此最好的选择可能是查询重写- 福雷斯特(Forrest)在其回答中对此进行了介绍


3

对我来说,where查询中的子句似乎引起了问题,并且是使用低估算值的原因OPTION(RECOMPILE)

我创建了一些测试数据,最后提出了两个解决方案,将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

我的估计甚至更糟,只有一个估计行,而返回了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

如预期的那样,很好的估计。

那么,我们可以改变什么以获得更好的估计,但仍在寻求我们的价值?

如果@UID是唯一的(如OP给出的示例),我们可以将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%正确的估计

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.