了解何时从查询/计划中删除 Order By 或 Sort 运算符

Beg*_*DBA 2 performance index sql-server query-performance performance-tuning

当我正在阅读和理解时,如果匹配索引以支持查询的键列以相同的方式排序,则需要避免 SQL 查询中不需要的 ORDER BY。

对于下面的数据库测试模式——

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,
    [Lastseen] [datetime] NULL,
    [DataKey1] [char] NOT NULL,
    [DataKey2] [char] NOT NULL,
    [RandomColumn] [nvarchar] 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)

如果我使用 ORDER BY 运行查询

SELECT [DemoID], [CaptureDate]
FROM [dbo].[DemoPartitionedTable]
WHERE CaptureDate>=CONVERT(datetime, '20190912', 112) AND
CaptureDate < CONVERT(datetime, '20191013', 112)
ORDER BY 
DemoID,
CaptureDate
Run Code Online (Sandbox Code Playgroud)

以下是来自 IO 和 Time 的统计数据

(受影响的 95703 行)表“工作表”。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 117,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。表 'DemoPartitionedTable'。扫描计数 16,逻辑读取 330,物理读取 9,预读读取 262,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

(1 行受影响)

SQL Server 执行时间:CPU 时间 = 297 毫秒,已用时间 = 750 毫秒。

在此处输入图片说明

根据我在上述情况下的理解,我们不需要 order by 因为索引已经对这些列进行了排序,那么为什么我看到下面的计划不好

所以如果我运行删除上面的排序

SELECT [DemoID], [CaptureDate]
FROM 
[dbo].[DemoPartitionedTable]
WHERE CaptureDate>=CONVERT(datetime, '20190912', 112) AND
CaptureDate < CONVERT(datetime, '20191013', 112)
--ORDER BY 
--DemoID,
--CaptureDate
Run Code Online (Sandbox Code Playgroud)

(受影响的 95703 行)表 'DemoPartitionedTable'。扫描计数 16,逻辑读取 330,物理读取 134,预读读取 269,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

(1 行受影响)

SQL Server 执行时间:CPU 时间 = 78 毫秒,已用时间 = 2791 毫秒。SQL Server 解析和编译时间:CPU 时间 = 0 毫秒,已用时间 = 0 毫秒。

在此处输入图片说明

计划没有昂贵的排序,但统计数据最差,为什么?

Dav*_*oft 5

根据我在上述情况下的理解,我们不需要 order by 因为索引已经对这些列进行了排序

这种理解是不正确的,该计划显示了一个原因。并行索引扫描不会按索引顺序输出行,因为每个线程在排序顺序中的不同位置读取。如果没有 ORDER BY 子句,您不能期望任何特定顺序的行。

并且索引没有已经排序的行。ORDER BY 在 (DemoID, CaptureDate) 上,但索引按 CaptureDate 进行分区。因此,如果查询跨越分区边界,DemoID 值将重新开始。

例如(DemoID,CaptureDate)顺序中的行:

(1,'20190912'),(2,'20190913'),(3,'20190912'),(4,'20190913')
Run Code Online (Sandbox Code Playgroud)

可以存储在多个分区上:

partition N
--------------
(1,'20190912')
(3,'20190912')

partition N+1
--------------
(2,'20190913')
(4,'20190913')
Run Code Online (Sandbox Code Playgroud)

因此,即使该计划使用单个线程来扫描索引,也需要下游排序。

计划没有昂贵的排序,但统计数据最差,为什么?

不,统计数据变得更好了。第二个查询具有相同的 330 次逻辑读取,CPU 时间仅为 78 毫秒,而第一个查询的 CPU 时间为 297 毫秒。elapsed time 的差异与更多的并行度和物理 IO 有关,这不是查询计划的属性。相反,它取决于运行查询时页面缓存的状态。

  • @BeginnerDBA,消除排序运算符并不总是可能的(或者我应该说更有效)。使用索引查找查找几行然后对它们进行排序可能比使用必须扫描表中每一行的排序索引更有效。 (2认同)