查询性能问题

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 名称而不是在查询中进行硬编码?

请指教

Pau*_*ite 5

问题中的表未分区。我假设预期的定义是:

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)

查询 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)

除了冗余1=1谓词之外,日期比较是一堆不匹配的类型。该datetimeCaptureDate转换为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 进行分区消除。

查询 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)

这个好一点。它避免转换列,但出于某种原因选择将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作为前导键的覆盖索引。如果您希望查询尽可能快,您应该创建一个这样的索引。

有关更多详细信息,请参阅我的文章为什么分区消除不起作用?

编写查询时请特别注意数据类型。