列大小增加后,为什么创建索引需要花费更长的时间?


18

我们的供应商更改了整个数据库中几乎所有列的列宽。该数据库约为7TB,可容纳9000多个表。我们正在尝试在具有55亿行的表上创建索引。在供应商升级之前,我们可以在2小时内创建索引。现在需要几天。他们所做的是将任何varchar(xx)的大小增加到varchar(256)。因此,大多数列以前是varchar(18)或varchar(75)等。

无论如何,主键由6列组成,其总宽度为126个字符。现在,升级后,主键为1283个字符,这违反了SQL Server 900个字符的限制。整个表格的列宽从合并的varchar总数为1049到合并的varchar总数为4009。

数据没有增加,表没有比所有列宽增加之前占用更多的“空间”,但是创建像索引这样简单的内容的性能现在花费了不合理的时间。

谁能解释为什么当唯一要做的就是增加列的大小时,创建和索引需要花费那么多的时间吗?

我们尝试创建的索引是非聚集的,因为pk是聚集索引。经过几次尝试创建索引后,我们放弃了。我认为它运行了4到5天没有完成。

我在非生产环境中尝试了此操作,方法是拍摄文件系统快照,并将数据库安装在安静的服务器上。

Answers:


12

Remus有益地指出,VARCHAR列的最大长度会影响估计的行大小,因此会影响SQL Server提供的内存授予。

我尝试做更多的研究,以扩展他的回答的“从此开始”。我没有完整或简洁的解释,但这是我所发现的。

复制脚本

我创建了一个完整的脚本,该脚本生成一个伪造的数据集,在该数据集上创建索引所需的时间大约是我的机器上该VARCHAR(256)版本的10倍。所使用的数据是完全一样的,但在第一表使用的实际最大长度1875915123,和5,而所有列使用的最大长度256在所述第二表中。


键入原始表

在这里,我们看到原始查询在大约20秒内完成,并且逻辑读取等于表的大小~1.5GB(195K页,每页8K)。

-- CPU time = 37674 ms,  elapsed time = 19206 ms.
-- Table 'testVarchar'. Scan count 9, logical reads 194490, physical reads 0
CREATE CLUSTERED INDEX IX_testVarchar
ON dbo.testVarchar (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO


键控VARCHAR(256)表

为了 VARCHAR(256)表,我们看到经过时间已大大增加。

有趣的是,CPU时间和逻辑读取都不会增加。鉴于表具有完全相同的数据,这是有道理的,但是并不能解释为什么经过时间如此之慢。

-- CPU time = 33212 ms,  elapsed time = 263134 ms.
-- Table 'testVarchar256'. Scan count 9, logical reads 194491
CREATE CLUSTERED INDEX IX_testVarchar256
ON dbo.testVarchar256 (s1, s2, s3, s4)
WITH (MAXDOP = 8) -- Same as my global MAXDOP, but just being explicit
GO


I / O和等待状态:原始

如果捕获更多细节(使用p_perfMon,我编写了一个过程),我们可以看到绝大多数I / O是在LOG文件上执行的。我们在实际的ROWS(主数据文件)上看到相对较少的I / O ,主要的等待类型是LATCH_EX,这表明内存中的页面争用。

根据保罗·兰德尔(Paul Randal)的说法,我们还可以看到我的旋转磁盘介于“坏”和“令人震惊地糟糕”之间:)

在此处输入图片说明


I / O和等待状态:VARCHAR(256)

为了 VARCHAR(256)版本,I / O和等待状态看起来完全不同!在这里,我们看到数据文件(ROWS)上I / O的大量增加,并且停顿时间现在使Paul Randal简单地说“哇!”。

#1等待类型现在是毫不奇怪的IO_COMPLETION。但是为什么会产生那么多的I / O?

在此处输入图片说明


实际查询计划:VARCHAR(256)

从查询计划中,我们可以看到Sort运算符VARCHAR(256)在查询版本中具有递归溢出(深度5级!)。(原始版本完全没有溢出。)

在此处输入图片说明


实时查询进度:VARCHAR(256)

我们可以使用sys.dm_exec_query_profiles来查看SQL 2014+中的实时查询进度。在原始版本中,整个Table ScanSort处理都没有任何溢出(spill_page_count始终保留0)。

VARCHAR(256)但是,在该版本中,我们可以看到页面溢出为Sort操作员迅速积累。这是查询完成前的查询进度的快照。此处的数据跨所有线程聚合。

在此处输入图片说明

如果我分别研究每个线程,我会看到2个线程在大约5秒钟内完成了排序(在表扫描上花费了15秒钟之后,整体@ 20秒钟)。如果所有线程都以该速率进行,则VARCHAR(256)索引创建将在与原始表大致相同的时间内完成。

但是,其余6个线程的进度要慢得多。这可能是由于内存的分配方式以及线程在溢出数据时被I / O阻止的方式所致。我不确定。

在此处输入图片说明


你能做什么?

您可以考虑尝试以下几种方法:

  • 与供应商合作以回滚到以前的版本。如果无法做到这一点,请让您对此更改不满意的供应商,以便他们可以考虑在将来的版本中还原它。
  • 当添加索引,可以考虑使用OPTION (MAXDOP X)其中X一个较小的数字比目前的服务器级别设置。当我OPTION (MAXDOP 2)在计算机上使用此特定数据集时,VARCHAR(256)版本在25 seconds(相比之下,使用8个线程需要3-4分钟!)。较高的并行度可能会加剧溢出行为。
  • 如果有可能进行额外的硬件投资,请在系统上分析I / O(可能的瓶颈),并考虑使用SSD来减少因溢出而导致的I / O延迟。


进一步阅读

保罗·怀特(Paul White)撰写了一篇不错的博客文章,介绍可能感兴趣的SQL Server内部结构。它确实谈到了溢出,线程偏斜和并行排序的内存分配。


11

在这两种情况下,中间排序表的估计将有所不同。VARCHAR(256)与“理想”请求相比,这将导致不同的内存授予请求(将更大),并且实际百分比可能要小得多。我猜这会导致排序时溢出。

从Geoff测试脚本(仅在10万行上),我可以清楚地看到排序估算的行大小之间的差异(141B与789B)。从这一点来看,事情是级联的。


8
我确信Paul将证明一个更为彻底和完整的答案,也许包括调用堆栈,这些将被开发团队用作学习材料。再次...
Remus Rusanu 2015年
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.