在 SQL Server 中拥有大量空表分区是否会产生可观的开销?

Mar*_*ith 10 sql-server partitioning

我最近继承了一个使用日期分区的项目,其中每日计划任务实现了过去 30 天和未来 60 天的滑动窗口方案。

实际上,插入的数据SYSUTCDATETIME()用于分区列,因此未来的 60 个分区始终为空。

这是一个需要解决的问题还是我应该让睡狗撒谎?

Mar*_*ith 14

拥有大量分区可能会导致显着的性能影响,因此在这种情况下删除相对大量的空分区可能会带来简单的好处。

设置

CREATE PARTITION FUNCTION [pf](datetime2(2)) AS RANGE RIGHT FOR VALUES (N'2020-05-06', N'2020-05-07', N'2020-05-08', N'2020-05-09', N'2020-05-10', N'2020-05-11', N'2020-05-12', N'2020-05-13', N'2020-05-14', N'2020-05-15', N'2020-05-16', N'2020-05-17', N'2020-05-18', N'2020-05-19', N'2020-05-20', N'2020-05-21', N'2020-05-22', N'2020-05-23', N'2020-05-24', N'2020-05-25', N'2020-05-26', N'2020-05-27', N'2020-05-28', N'2020-05-29', N'2020-05-30', N'2020-05-31', N'2020-06-01', N'2020-06-02', N'2020-06-03', N'2020-06-04', N'2020-06-05', N'2020-06-06', N'2020-06-07', N'2020-06-08', N'2020-06-09', N'2020-06-10', N'2020-06-11', N'2020-06-12', N'2020-06-13', N'2020-06-14', N'2020-06-15', N'2020-06-16', N'2020-06-17', N'2020-06-18', N'2020-06-19', N'2020-06-20', N'2020-06-21', N'2020-06-22', N'2020-06-23', N'2020-06-24', N'2020-06-25', N'2020-06-26', N'2020-06-27', N'2020-06-28', N'2020-06-29', N'2020-06-30', N'2020-07-01', N'2020-07-02', N'2020-07-03', N'2020-07-04', N'2020-07-05',N'2020-07-06', N'2020-07-07', N'2020-07-08', N'2020-07-09', N'2020-07-10', N'2020-07-11', N'2020-07-12', N'2020-07-13', N'2020-07-14', N'2020-07-15', N'2020-07-16', N'2020-07-17', N'2020-07-18', N'2020-07-19', N'2020-07-20', N'2020-07-21', N'2020-07-22', N'2020-07-23', N'2020-07-24', N'2020-07-25', N'2020-07-26', N'2020-07-27', N'2020-07-28', N'2020-07-29', N'2020-07-30', N'2020-07-31', N'2020-08-01', N'2020-08-02', N'2020-08-03')

CREATE PARTITION SCHEME [ps] AS PARTITION [pf] ALL TO ([PRIMARY])

CREATE TABLE T1(X INT PRIMARY KEY);

INSERT INTO T1
SELECT TOP 30000 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM   sys.all_objects o1,
       sys.all_objects o2

CREATE TABLE T2
(
   X        INT,
   dt2      DATETIME2(2),
   OtherCol CHAR(100),
   PRIMARY KEY(X, dt2) ON ps(dt2)
);

INSERT INTO T2 (X, dt2)
SELECT TOP 21474836 ROW_NUMBER() OVER (ORDER BY @@SPID),
                    DATEADD(MILLISECOND, 100 * ROW_NUMBER() OVER (ORDER BY @@SPID), '2020-05-05')
FROM   sys.all_objects o1,
       sys.all_objects o2,
       sys.all_objects o3 
Run Code Online (Sandbox Code Playgroud)

这设置了一种类似于问题中描述的情况。前 25 个分区有数据,其余 66 个分区为空。

询问

SET STATISTICS TIME ON;

SELECT COUNT(*)
FROM T1 INNER JOIN T2 ON T1.X = T2.X
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

以上对我来说花了 9.8 秒。它需要扫描整个T2.

我们有一个带有前导列的分区对齐索引,X那么如果我们强制循环连接会发生什么?

SELECT COUNT(*)
FROM T1 INNER LOOP JOIN T2 ON T1.X = T2.X
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

循环连接实际上稍微差了一点(慢了 0.4 秒)。30,000 次搜索无法进行任何分区消除,并且都需要检查 91 个分区,这大大增加了所需的工作量。

最后一次尝试...

SELECT COUNT(*)
FROM T1
CROSS APPLY (SELECT TOP 1 $partition.pf(dt2) FROM T2 ORDER BY $partition.pf(dt2) DESC) CA(MaxPtn)
INNER LOOP JOIN T2 ON T1.X = T2.X AND $partition.pf(dt2) <= CA.MaxPtn
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这对我来说在 3.7 秒内完成。不同之处在于查询现在首先识别顶部非空分区,并在随后的查找中使用此值来避免需要为空分区做任何工作。

所以我的结论是,空分区肯定会对查询性能产生显着影响,如果执行不包含分区列作为谓词的查询,则可能应该解决。

使用以下内容删除除一个空的尾部分区之外的所有分区...

DECLARE @dt datetime2(2) = '2020-08-03'

WHILE @dt >=  '2020-05-31'
BEGIN
ALTER PARTITION FUNCTION pf()  
 MERGE RANGE (@dt)  
 SET @dt = DATEADD(DAY, -1, @dt)
END
Run Code Online (Sandbox Code Playgroud)

... 提供相同的加速(并且不再需要使用提示来获取循环连接计划,因为搜索的估计操作员成本从523.328原始INNER LOOP JOIN情况下降到149.546

在此处输入图片说明