Dan*_*ler 18 sql-server database-internals
我们的供应商更改了整个数据库中几乎每一列的列宽。数据库大约 7TB,9000 多个表。我们正在尝试在具有 55 亿行的表上创建索引。在供应商升级之前,我们可以在 2 小时内创建索引。现在需要几天时间。他们所做的是将任何 varchar(xx) 大小增加到 varchar(256)。所以大多数列曾经是 varchar(18) 或 varchar(75) 等。
无论如何,主键由 6 列组成,组合宽度为 126 个字符。现在升级后,主键是 1283 个字符,这违反了 SQL Server 900 个字符的限制。整个表列宽度从 1049 的总组合 varchar 计数变为 4009 的总组合 varchar 计数。
数据没有增加,表不会占用比所有列宽增加之前更多的“空间”,但是创建像索引这样简单的东西的性能现在花费了不合理的时间。
任何人都可以解释为什么当唯一要做的就是增加列的大小时,为什么创建和索引需要这么长时间?
我们尝试创建的索引是非聚集的,因为 pk 是聚集索引。在多次尝试创建索引后,我们放弃了。我认为它运行了 4 或 5 天而没有完成。
我在非生产环境中尝试了这一点,方法是拍摄文件系统快照并将数据库放在更安静的服务器上。
Geo*_*son 12
Remus 有用地指出,VARCHAR
列的最大长度会影响估计的行大小,因此会影响 SQL Server 提供的内存授权。
我试图做更多的研究来扩展他的答案中“从这件事上级联”部分。我没有完整或简洁的解释,但这是我发现的。
复制脚本
我创建了一个完整的脚本,它生成一个假数据集,在该VARCHAR(256)
版本的机器上创建索引所需的时间大约是我机器上的 10 倍。所使用的数据是完全一样的,但在第一表使用的实际最大长度18
,75
,9
,15
,123
,和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
Run Code Online (Sandbox Code Playgroud)
键入 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
Run Code Online (Sandbox Code Playgroud)
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 Scan
和Sort
都经过处理,没有任何溢出(spill_page_count
始终保持0
)。
VARCHAR(256)
然而,在版本中,我们可以看到页面溢出为Sort
操作员迅速积累。这是查询完成之前查询进度的快照。这里的数据跨所有线程聚合。
如果我单独深入研究每个线程,我会看到 2 个线程在大约 5 秒内完成排序(总共 20 秒,在表扫描上花费 15 秒后)。如果所有线程都以这种速度进行,VARCHAR(256)
索引创建将在与原始表大致相同的时间内完成。
但是,其余 6 个线程的进度要慢得多。这可能是由于内存分配方式以及线程在溢出数据时被 I/O 阻塞的方式。不过我不确定。
你能做什么?
您可能会考虑尝试以下几种方法:
OPTION (MAXDOP X)
其中X
一个较小的数字比目前的服务器级别设置。当我OPTION (MAXDOP 2)
在我的机器上使用这个特定的数据集时,VARCHAR(256)
版本完成25 seconds
(与 8 个线程的 3-4 分钟相比!)。更高的并行度可能会加剧溢出行为。
进一步阅读
Paul White 有一篇关于SQL Server 内部结构的不错的博客文章,您可能会感兴趣。它确实谈到了并行排序的溢出、线程倾斜和内存分配。
Rem*_*anu 11
中间排序表将在两种情况下进行不同的估计。VARCHAR(256)
与“理想”请求相比,这将导致不同的内存授予请求(将更大),并且在百分比方面可能会导致实际授予要小得多。我想这会导致排序过程中发生溢出。
从 Geoff 测试脚本(仅在 100k 行上)我可以清楚地看到排序估计行大小的差异(141B 与 789B)。从此事情级联。
归档时间: |
|
查看次数: |
455 次 |
最近记录: |