Beg*_*DBA -1 performance sql-server partitioning sql-server-2012 query-performance performance-tuning
使用 Demo from here重现我的问题,更改表结构如下,并将演示分区函数修改为 datetime
CREATE TABLE [dbo].[DemoPartitionedTable](
[DemoID] [int] IDENTITY(1,1) NOT NULL,
[SomeData] [sysname] NOT NULL,
[CaptureDate] [datetime] NULL,
CONSTRAINT [PK_DemoPartitionedTable] UNIQUE NONCLUSTERED
(
[DemoID] ASC,
[CaptureDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
Run Code Online (Sandbox Code Playgroud)
用户运行如下查询(查询 1)
SELECT [DemoID], [SomeData], [CaptureDate] FROM
[dbo].[DemoPartitionedTable] WHERE (1=1) and (1=1) and
CONVERT(varchar(10),CaptureDate,112) between 20190912 and 20190912
Run Code Online (Sandbox Code Playgroud)
计划是https://www.brentozar.com/pastetheplan/?id=r1veZhovH
大约需要 4 小时才能返回 500 万行
如果我使用如下分区键来改进上面的代码(查询 2)
SELECT [DemoID], [SomeData], [CaptureDate] FROM
[dbo].[DemoPartitionedTable] WHERE (1=1) and (1=1) and
CaptureDate>= cast('2019-09-12' as date) and
CaptureDate< cast('2019-09-13' as date)
Run Code Online (Sandbox Code Playgroud)
这将在大约 40 分钟内完成
计划https://www.brentozar.com/pastetheplan/?id=BkDYl3svB
现在,如果我在下面运行,它会在 10 分钟内完成并根据需要直接扫描当天的特定分区,而前 2 个查询扫描所有分区(查询 3)
SELECT [DemoID],
[SomeData],
[CaptureDate]
FROM [dbo].[DemoPartitionedTable]
WHERE (1=1)
AND (1=1)
AND $partition.DemoPartitionFunction(CaptureDate)>=$partition.DemoPartitionFunction('09/12/2019')
AND $partition.DemoPartitionFunction(CaptureDate)<$partition.DemoPartitionFunction('09/13/2019')
Run Code Online (Sandbox Code Playgroud)
计划https://www.brentozar.com/pastetheplan/?id=S1j9CjsPB
上面的问题是用户必须在查询之前找到我的查询 3 中使用的 PF 名称。为什么查询 2 不能执行类似于查询并使用分区消除?有没有办法对查询进行编码以查找 PF 名称而不是在查询中进行硬编码?
请指教
问题中的表未分区。我假设预期的定义是:
CREATE PARTITION FUNCTION DemoPartitionFunction (datetime)
AS RANGE RIGHT
FOR VALUES (DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -7),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -6),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -5),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -4),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -3),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -2),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -1),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 0),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 1),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 2),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 3),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 4),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 5),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 6),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 7));
CREATE PARTITION SCHEME DemoPartitionScheme
AS PARTITION DemoPartitionFunction
ALL TO ([DEFAULT]);
CREATE TABLE [dbo].[DemoPartitionedTable](
[DemoID] [int] IDENTITY(1,1) NOT NULL,
[SomeData] [sysname] NOT NULL,
[CaptureDate] [datetime] NULL,
CONSTRAINT [PK_DemoPartitionedTable] UNIQUE NONCLUSTERED
(
[DemoID] ASC,
[CaptureDate] ASC
)
ON DemoPartitionScheme(CaptureDate)
) ON DemoPartitionScheme(CaptureDate);
Run Code Online (Sandbox Code Playgroud)
SELECT [DemoID], [SomeData], [CaptureDate] FROM
[dbo].[DemoPartitionedTable] WHERE (1=1) and (1=1) and
CONVERT(varchar(10),CaptureDate,112) between 20190912 and 20190912
Run Code Online (Sandbox Code Playgroud)
除了冗余1=1
谓词之外,日期比较是一堆不匹配的类型。该datetime
列CaptureDate转换为varchar(10)
没有明确的风格,那么相比integers
。
堆扫描的残差谓词反映了这种混乱:
CONVERT_IMPLICIT(int,CONVERT(varchar(10),[dbo].[DemoPartitionedTable].[CaptureDate],112),0)>=(20190912)
AND CONVERT_IMPLICIT(int,CONVERT(varchar(10),[dbo].[DemoPartitionedTable].[CaptureDate],112),0)<=(20190912)
Run Code Online (Sandbox Code Playgroud)
这些谓词不能使用索引,也不能帮助 SQL Server 进行分区消除。
SELECT [DemoID], [SomeData], [CaptureDate] FROM
[dbo].[DemoPartitionedTable] WHERE (1=1) and (1=1) and
CaptureDate>= cast('2019-09-12' as date) and
CaptureDate< cast('2019-09-13' as date)
Run Code Online (Sandbox Code Playgroud)
这个好一点。它避免转换列,但出于某种原因选择将datetime
CaptureDate列与date
数据类型进行比较。这些是不同的数据类型。计划中没有关于类型转换的警告,因为 SQL Server 可以在编译期间将date
类型化文字转换为datetime
。尽管如此,类型不一致足以防止分区消除。
SELECT
DPT.DemoID,
DPT.SomeData,
DPT.CaptureDate
FROM dbo.DemoPartitionedTable AS DPT
WHERE
1 = 1
AND DPT.CaptureDate >= CONVERT(datetime, '20190912', 112)
AND DPT.CaptureDate < CONVERT(datetime, '20190913', 112);
Run Code Online (Sandbox Code Playgroud)
上述查询将datetime
CaptureDate列与datetime
文字进行比较。请注意显式样式的使用,因此 SQL Server 知道字符串的格式。在处理日期和时间时,您通常应该更喜欢CONVERT
使用正确的样式CAST
。
该1 = 1
谓词目的是避免简单的参数。
此查询允许 SQL Server 执行分区消除,如堆表上的Seek Predicate所示:
CREATE PARTITION FUNCTION DemoPartitionFunction (datetime)
AS RANGE RIGHT
FOR VALUES (DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -7),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -6),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -5),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -4),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -3),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -2),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), -1),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 0),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 1),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 2),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 3),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 4),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 5),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 6),
DATEADD(dd, DATEDIFF(dd, 0, GETUTCDATE()), 7));
CREATE PARTITION SCHEME DemoPartitionScheme
AS PARTITION DemoPartitionFunction
ALL TO ([DEFAULT]);
CREATE TABLE [dbo].[DemoPartitionedTable](
[DemoID] [int] IDENTITY(1,1) NOT NULL,
[SomeData] [sysname] NOT NULL,
[CaptureDate] [datetime] NULL,
CONSTRAINT [PK_DemoPartitionedTable] UNIQUE NONCLUSTERED
(
[DemoID] ASC,
[CaptureDate] ASC
)
ON DemoPartitionScheme(CaptureDate)
) ON DemoPartitionScheme(CaptureDate);
Run Code Online (Sandbox Code Playgroud)
仍然有一个残差谓词应用于分区中的每一行:
[dbo].[DemoPartitionedTable].[CaptureDate] as [DPT].[CaptureDate]>='2019-09-12 00:00:00.000'
AND [dbo].[DemoPartitionedTable].[CaptureDate] as [DPT].[CaptureDate]<'2019-09-13 00:00:00.000'
Run Code Online (Sandbox Code Playgroud)
这是因为该表缺少以CaptureDate作为前导键的覆盖索引。如果您希望查询尽可能快,您应该创建一个这样的索引。
有关更多详细信息,请参阅我的文章为什么分区消除不起作用?
编写查询时请特别注意数据类型。