在 SQL Server 2016 及更高版本上重建中的错误?

Nee*_*rma 8 performance sql-server database-internals

问题总结

即使在REBUILD索引之后,碎片聚集索引也表现不佳。如果索引是,REORGANIZED则给定表/索引的性能增加。

我只在 SQL Server 2016 及更高版本上看到这种异常行为,我已经在不同的硬件和不同的版本上测试了这种情况(所有个人机器都具有传统的旋转硬盘)。如果需要更多信息,请告诉我。

这是 SQL Server 2016 及更高版本中的错误吗?


如果有人愿意,我可以提供完整的详细信息和脚本分析,但现在不提供,因为脚本非常大并且会在问题中占用大量空间。

如果您有 SQL Server 2016 及更高版本,请在您的 DEV 环境中测试从下面提供的链接中获取的示例脚本的较短版本。

脚本

-- SECTION 1
/*
Create a Test Folder in the machine and spefiy the drive in which you created
*/
USE MASTER

CREATE DATABASE RebuildTest
ON 
( NAME = 'RebuildTest',
    FILENAME = 'F:\TEST\RebuildTest_db.mdf',
    SIZE = 200MB,
    MAXSIZE = UNLIMITED,
    FILEGROWTH = 50MB )
LOG ON
( NAME = 'RebuildTest_log',
    FILENAME = 'F:\TEST\RebuildTest_db.ldf',
    SIZE = 100MB,
      MAXSIZE = UNLIMITED,
    FILEGROWTH = 10MB ) ;
GO

BEGIN TRAN

USE RebuildTest

select  top 1000000
row_number () over ( order by (Select null)) n into Numbers from
sys.all_columns  a cross  join  sys.all_columns

CREATE TABLE [DBO].FRAG3 (
Primarykey int NOT NULL ,
SomeData3 char(1000) NOT NULL )

ALTER TABLE DBO.FRAG3
ADD CONSTRAINT PK_FRAG3 PRIMARY KEY (Primarykey)

INSERT INTO [DBO].FRAG3
SELECT n , 'Some text..'
FROM Numbers
Where N/2 = N/2.0

Update DBO.FRAG3 SET Primarykey =  Primarykey-500001
Where  Primarykey>500001

COMMIT

 -- SECTION 2

SELECT @@VERSION

/*                                                       BEGIN PART FRAG1.1     */

----- BEGIN CLEANBUFFER AND DATABASE AND MEASURE TIME
CHECKPOINT;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS TIME ON
Select Count_Big (*) From [DBO].[FRAG3] Where Primarykey >0 Option (MAxDop 1)
SET STATISTICS TIME OFF

----- END CLEANBUFFER AND DATABASE AND MEASURE TIME

-------------BEGIN PART FRAG1.2: REBUILD THE INDEX AND TEST AGAIN

--BEGIN Rebuild the Index
Alter Table [DBO].[FRAG3] REBUILD
--END  Rebuild the Index

----- BEGIN CLEANBUFFER FROM DATABASE AND MEASURE TIME
CHECKPOINT;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS TIME ON
Select Count_Big (*) From [DBO].[FRAG3] Where Primarykey >0 Option (MAxDop 1)
SET STATISTICS TIME OFF
----- END CLEANBUFFER  FROM DATABASE AND MEASURE TIME

--BEGIN REORGANIZE the Index
ALTER INDEX ALL ON [DBO].[FRAG3] REORGANIZE ;
--END REORGANIZE the Index

----- BEGIN CLEANBUFFER  FROM DATABASE AND MEASURE TIME
CHECKPOINT;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS TIME ON
Select Count_Big (*) From [DBO].[FRAG3] Where Primarykey >0 Option (MAxDop 1)
SET STATISTICS TIME OFF
----- END CLEANBUFFER  FROM DATABASE AND MEASURE TIME

-------------BEGIN PART FRAG1.4: REBUILD THE INDEX AND TEST AGAIN

--BEGIN Rebuild the Index
Alter Table [DBO].[FRAG3] REBUILD
--END  Rebuild the Index

----- BEGIN CLEANBUFFER FROM DATABASE AND MEASURE TIME
CHECKPOINT;
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS TIME ON
Select Count_Big (*) From [DBO].[FRAG3] Where Primarykey >0 Option (MAxDop 1)
SET STATISTICS TIME OFF
----- END CLEANBUFFER  FROM DATABASE AND MEASURE TIME

-------------END PART FRAG1.4: REBUILD THE INDEX AND TEST AGAIN
Run Code Online (Sandbox Code Playgroud)

结果

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明

Crystal Disk Mark 测试结果

在此处输入图片说明

在此处输入图片说明

细节

我在 SQL Server 2016 及更高版本上看到存储引擎(可能)的异常行为,我创建了一个高度碎片化的表(读取碎片问题)演示目的,然后重建它。

即使在重建索引后,性能也没有按预期增加。为了确保数据访问模式应该按关键顺序而不是 IAM 驱动(分配顺序扫描),我使用了范围谓词。

最初,我认为 SQL Server 2016 及更高版本可能更适合大型扫描。为了检查这一点,我调整了页数和行数,但性能模式没有改变。我在个人系统上测试了所有内容,所以我可以说没有其他用户活动正在进行。

我也在其他硬件上测试了这种行为(都有传统的旋转硬盘)。性能模式几乎相同。

我已经检查了那里的等待统计数据似乎都很正常PAGELATCH_IO(使用 Paul Randal 脚本)。我使用 DMV 检查了数据页sys.dm_db_database_page_allocations,看起来也不错。

如果我重组表或将所有数据移动到具有相同索引定义的新表,磁盘 IO 性能会增加。我已经用 perfmon 进行了检查,似乎重组索引 / 新表可以利用顺序 IO 和重建索引仍然使用随机读取,尽管两者都有几乎相同的内部和外部碎片数据页。

我在我捕获的系统上附加了完整的查询和结果。如果你们有 SQL Server 2016 及更高版本的 DEV 框,请检查这个并分享你的结果。

警告:此测试包含一些未记录的命令,DROPCLEANBUFFERS因此根本不在生产服务器上运行。

如果这真的是一个错误,我想我应该提交它。

所以问题是:它真的是一个错误还是我遗漏了什么;)

链接(粘贴箱)

1 碎片表创建

2 支持 SP 在表创建后运行

3 测试

4 重建的任何问题

5 将数据放入新表

链接:(谷歌驱动器)

6 在我的系统上下载结果

Dan*_*man 13

有问题的查询使用SQL Server 预读功能。通过预读性能优化,SQL Server 存储引擎在扫描期间预取数据,以便在查询需要时页面已经在缓冲区缓存中,从而减少查询执行期间等待数据的时间。

预读的执行时间差异反映了存储系统和 Windows API 处理大 IO 大小的好坏程度以及 SQL Server 预读行为的差异(因版本而异)。较旧的 SQL Server 版本(上述文章中的 SQL Server 2008 R2)将预取限制为 512K IO 大小,而 SQL Server 2016 及更高版本发出更大大小的预读 IO,以利用现代生产级商品硬件(RAID 和 SSD)的功能。请记住,SQL Server 通常经过优化,可在发布时在当前一代硬件上运行,利用更大的处理器缓存、NUMA 架构和存储系统 IOPS/带宽能力。此外,企业/开发人员版本还比较小版本更积极地执行预取,以更大限度地提高吞吐量。

为了更好地理解 SQL 2008 R2 与更高版本相比性能不同的原因,我在具有不同 SQL Server Developer Edition 版本的旧物理机上执行了脚本的修改版本。该测试盒同时具有 7200 RPM HDD 和 SATA SSD,允许在同一台机器上针对不同的存储系统和 SQL 版本运行相同的测试。我在每次测试期间使用扩展事件跟踪捕获了 file_read 和 file_read_completed 事件,以便对 IO 和时序进行更详细的分析。

结果显示,除了 SQL Server 2012 和更高版本在集群索引重建后,在单个 HDD 轴上的所有 SQL Server 版本和存储系统类型的性能大致相当。有趣的是,XE 跟踪仅在 SQL Server 2008 R2 中的预读扫描期间显示“连续”模式;跟踪显示所有其他版本都使用了“Scatter/Gather”模式。我不能说这种差异是否有助于提高性能。

此外,跟踪数据的分析显示 SQL 2016 在预读扫描期间发出更大的读取,平均 IO 大小因存储类型而异。这并不一定意味着 SQL Server 会根据物理硬件调整预读 IO 大小,而是它可能会根据未知测量值调整大小。存储引擎使用的启发式方法没有记录,可能因版本和补丁级别而异。

以下是测试时间的摘要。当我有更多时间时,我将添加从跟踪中收集的更多信息(不幸的是,IO 大小在 SQL Server 2008 R2 XE 中不可用)。总之,IO 配置文件因版本和存储类型而异。在这些测试中,SQL Server 2014 版本的平均 IO 大小从未超过 512K,而 SQL Server 2016 在单个 IO 中读取超过 4MB。SQL 2016 测试中未完成读取的数量也少得多,因为 SQL Server 完成相同工作的 IO 请求较少。

SQL_Version Storage Device         Test                   Duration
SQL 2008 R2    HDD             initial table            00:00:03.686
SQL 2012       HDD             initial table            00:00:03.725
SQL 2014       HDD             initial table            00:00:03.706
SQL 2016       HDD             initial table            00:00:03.654
SQL 2008 R2    HDD             fragmented table         00:00:07.796
SQL 2012       HDD             fragmented table         00:00:08.026
SQL 2014       HDD             fragmented table         00:00:07.837
SQL 2016       HDD             fragmented table         00:00:06.097
SQL 2008 R2    HDD             after rebuild            00:00:06.962
SQL 2012       HDD             after rebuild            00:00:21.129
SQL 2014       HDD             after rebuild            00:00:19.501
SQL 2016       HDD             after rebuild            00:00:21.377
SQL 2008 R2    HDD             after reorg              00:00:04.103
SQL 2012       HDD             after reorg              00:00:03.974
SQL 2014       HDD             after reorg              00:00:04.076
SQL 2016       HDD             after reorg              00:00:03.610
SQL 2008 R2    HDD             after reorg and rebuild  00:00:07.201
SQL 2012       HDD             after reorg and rebuild  00:00:21.839
SQL 2014       HDD             after reorg and rebuild  00:00:20.199
SQL 2016       HDD             after reorg and rebuild  00:00:21.782
SQL 2008 R2    SATA SSD        initial table            00:00:02.083
SQL 2012       SATA SSD        initial table            00:00:02.071
SQL 2014       SATA SSD        initial table            00:00:02.074
SQL 2016       SATA SSD        initial table            00:00:02.066
SQL 2008 R2    SATA SSD        fragmented table         00:00:03.134
SQL 2012       SATA SSD        fragmented table         00:00:03.129
SQL 2014       SATA SSD        fragmented table         00:00:03.129
SQL 2016       SATA SSD        fragmented table         00:00:03.113
SQL 2008 R2    SATA SSD        after rebuild            00:00:02.065
SQL 2012       SATA SSD        after rebuild            00:00:02.097
SQL 2014       SATA SSD        after rebuild            00:00:02.071
SQL 2016       SATA SSD        after rebuild            00:00:02.078
SQL 2008 R2    SATA SSD        after reorg              00:00:02.064
SQL 2012       SATA SSD        after reorg              00:00:02.082
SQL 2014       SATA SSD        after reorg              00:00:02.067
SQL 2016       SATA SSD        after reorg              00:00:02.072
SQL 2008 R2    SATA SSD        after reorg and rebuild  00:00:02.078
SQL 2012       SATA SSD        after reorg and rebuild  00:00:02.087
SQL 2014       SATA SSD        after reorg and rebuild  00:00:02.087
SQL 2016       SATA SSD        after reorg and rebuild  00:00:02.079
Run Code Online (Sandbox Code Playgroud)

我还在具有 HDD 支持的 SAN 的 VM 上运行这些测试,并观察到与 SATA SSD 相似的性能。所以最重要的是,这个性能问题只发生在单轴 HDD 数据文件中,这仅在 PC 上常见,而不是现代生产系统。这是否应该被视为性能回归错误是有问题的,但我会联系看看我是否可以获得更多信息。

编辑

我联系了我的联系人,Erland Sommarskog 指出 sys.dm_db_index_physical_stats 在更高版本中报告的更高的片段计数。深入挖掘,我注意到 REBUILD 语句是一个并行查询。这意味着并行重建实际上可能会增加碎片计数(甚至在没有碎片的表上引入碎片),因为空间分配是并行完成的。除了预读扫描外,这通常不会影响性能,并且如测试所示,对于单轴旋转介质尤其如此。这是所有 SQL 版本中的一个考虑因素。

Paul Randal 指出这是设计使然,并参考了此文档以获取更多信息。对于将利用预读扫描(例如数据仓库)的工作负载进行索引重建的最佳实践是重建WITH (MAXDOP = 1)