优化 2,135,044,521 行表上的索引

Ste*_*old 10 performance sql-server index-tuning azure-sql-database query-performance

我有一张大表的 I/O 问题。

一般统计

该表具有以下主要特征:

  • 环境:Azure SQL 数据库(层为 P4 Premium (500 DTU))
  • 行数:2,135,044,521
  • 1,275 个已用分区
  • 聚集索引和分区索引

模型

这是表的实现:

CREATE TABLE [data].[DemoUnitData](
    [UnitID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [Value1] [decimal](18, 2) NULL,
    [Value2] [decimal](18, 2) NULL,
    [Value3] [decimal](18, 2) NULL,
    CONSTRAINT [PK_DemoUnitData] PRIMARY KEY CLUSTERED 
    (
        [UnitID] ASC,
        [Timestamp] ASC
    )
)
GO

ALTER TABLE [data].[DemoUnitData] WITH NOCHECK ADD CONSTRAINT [FK_DemoUnitData_Unit] FOREIGN KEY([UnitID])
REFERENCES [model].[Unit] ([ID])
GO

ALTER TABLE [data].[DemoUnitData] CHECK CONSTRAINT [FK_DemoUnitData_Unit]
GO
Run Code Online (Sandbox Code Playgroud)

分区与此有关:

CREATE PARTITION SCHEME [DailyPartitionSchema] AS PARTITION [DailyPartitionFunction] ALL TO ([PRIMARY])

CREATE PARTITION FUNCTION [DailyPartitionFunction] (datetime) AS RANGE RIGHT
FOR VALUES (N'2017-07-25T00:00:00.000', N'2017-07-26T00:00:00.000', N'2017-07-27T00:00:00.000', ... )
Run Code Online (Sandbox Code Playgroud)

服务质量

我认为索引和统计数据每晚都通过增量重建/重组/更新得到很好的维护。

这些是使用最频繁的索引分区的当前索引统计信息:

分区统计

这些是使用最频繁的分区的当前统计属性:

统计数据

问题

我对表运行了一个高频率的简单查询。

SELECT [UnitID]
    ,[Timestamp]
    ,[Value1]
    ,[Value2]
    ,[Value3]
FROM [data].[DemoUnitData]
WHERE [UnitID] = 8877 AND [Timestamp] >= '2018-03-01' AND [Timestamp] < '2018-03-13'
OPTION (MAXDOP 1)
Run Code Online (Sandbox Code Playgroud)

超数

执行计划如下所示:https : //www.brentozar.com/pastetheplan/?id=rJvI_4TtG

我的问题是这些查询会产生大量的 I/O 操作,从而导致PAGEIOLATCH_SH等待瓶颈。

顶部等待

我读过PAGEIOLATCH_SH等待通常与未充分优化的索引有关。您对如何减少 I/O 操作有什么建议吗?也许通过添加更好的索引?


答案 1 - 与@S4V1N 的评论有关

发布的查询计划来自我在 SSMS 中执行的查询。在您发表评论后,我对服务器历史进行了一些研究。从服务中执行的 accual 查询看起来有点不同(与 EntityFramework 相关)。

(@p__linq__0 bigint,@p__linq__1 datetime2(7),@p__linq__2 datetime2(7)) 

SELECT 1 AS [C1], [Extent1] 
   .[Timestamp] AS [Timestamp], [Extent1] 
   .[Value1] AS [Value1], [Extent1] 
   .[Value2] AS [Value2], [Extent1] 
   .[Value3] AS [Value3]  
FROM [data].[DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2) OPTION (MAXDOP 1) 
Run Code Online (Sandbox Code Playgroud)

此外,计划看起来不同:

https://www.brentozar.com/pastetheplan/?id=H1fhALpKG

或者

https://www.brentozar.com/pastetheplan/?id=S1DFQvpKz

正如您在此处看到的,我们的数据库性能几乎不受此查询的影响。

顶级 SQL

答案 2 - 与@Joe Obbish 的答案有关

为了测试解决方案,我用一个简单的 SqlCommand 替换了实体框架。结果是惊人的性能提升!

查询计划现在与 SSMS 中的相同,每次执行的逻辑读取和写入下降到约 8 次。

整体 I/O 负载下降到几乎为 0! 输入/输出下降

这也解释了为什么我将分区范围从每月更改为每天后性能大幅下降。分区消除的缺失导致要扫描的分区更多。

Joe*_*ish 7

PAGEIOLATCH_SH如果您能够更改 ORM 生成的数据类型,则您可能能够减少对该查询的等待。Timestamp表中的列的数据类型为 ,DATETIME但参数@p__linq__1@p__linq__2数据类型为DATETIME2(7)。这种差异就是 ORM 查询的查询计划比您发布的第一个具有硬编码搜索过滤器的查询计划复杂得多的原因。你也可以在 XML 中得到一个提示:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@p__linq__1],NULL,(22))">
Run Code Online (Sandbox Code Playgroud)

照原样,使用 ORM 查询无法消除任何分区。对于分区函数中定义的每个分区,您至少会获得一些逻辑读取,即使您只是在搜索一天的数据。在每个分区中,您都会进行索引查找,因此 SQL Server 移动到下一个分区不需要很长时间,但可能所有 IO 都在累加。

我做了一个简单的复制来确定。分区函数中定义了 11 个分区。对于此查询:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime2(7) = '20180103';
DECLARE @p__linq__2 datetime2(7) = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;
Run Code Online (Sandbox Code Playgroud)

这是 IO 的样子:

表“DemoUnitData”。扫描计数 11,逻辑读取 40

当我修复数据类型时:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime = '20180103';
DECLARE @p__linq__2 datetime = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;
Run Code Online (Sandbox Code Playgroud)

由于分区消除,IO 减少:

表“DemoUnitData”。扫描计数 2,逻辑读取 8