sql-server - View 上最大(日期)查询的奇怪查询计划

标签 sql-server sql-execution-plan

我有一个包含 4 个年度表格的 View :

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE VIEW [dbo].[BGT_BETWAYDETAILS]
WITH SCHEMABINDING
AS
SELECT [bwd_BetTicketNr] ,
    [bwd_LineID] [int] ,
    [bwd_ResultID] [bigint] NOT NULL,
    [bwd_DateModified]  ,
    [bwd_DateModifiedTrunc]  ,
    [bwd_LineMaxPayout] 
FROM   [dbo].[BGT_BETWAYDETAILS_2020]
UNION ALL
SELECT [bwd_BetTicketNr] ,
    [bwd_LineID] [int] ,
    [bwd_DateModified]  ,
    [bwd_DateModifiedTrunc]  ,
    [bwd_LineMaxPayout] 
FROM   [dbo].[BGT_BETWAYDETAILS_2019]
UNION ALL
SELECT [bwd_BetTicketNr] ,
    [bwd_LineID] [int] ,
    [bwd_DateModified]  ,
    [bwd_DateModifiedTrunc]  ,
    [bwd_LineMaxPayout] 
FROM   [dbo].[BGT_BETWAYDETAILS_2018]
UNION ALL
SELECT [bwd_BetTicketNr] ,
    [bwd_LineID] [int] ,
    [bwd_DateModified]  ,
    [bwd_DateModifiedTrunc]  ,
    [bwd_LineMaxPayout] 
FROM   [dbo].[BGT_BETWAYDETAILS_2017];
GO

每个表都有以下结构:

CREATE TABLE [dbo].[BGT_BETWAYDETAILS_2020]
(
    [bwd_BetTicketNr] [bigint] NOT NULL,
    [bwd_LineID] [int] NOT NULL,
    [bwd_ResultID] [bigint] NOT NULL,
    [bwd_DateModified] [datetime] NULL,
    [bwd_DateModifiedTrunc] [date] NULL,
    [bwd_LineMaxPayout] [decimal](18, 4) NULL,

    CONSTRAINT [CSTR__BGT_BETWAYDETAILS_2020_CKEY] 
        PRIMARY KEY CLUSTERED ([bwd_BetTicketNr] ASC, [bwd_LineID] ASC, [bwd_ResultID] ASC)
                    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                          IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                          ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

我已添加非聚集索引

CREATE NONCLUSTERED INDEX [NCI__DATEMODIFIED] 
ON [dbo].[BGT_BETWAYDETAILS_2020] ([bwd_DateModifiedTrunc] ASC)
                    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                          SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
                          ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

我正在运行以下 3 个查询:

SELECT COALESCE(MAX([bwd_DateModifiedTrunc]), '2019-01-01') AS next_date
FROM [dbo].[BGT_BETWAYDETAILS_2020]

SELECT COALESCE(MAX([bwd_DateModifiedTrunc]), '2019-01-01') AS next_date
FROM [dbo].[BGT_BETWAYDETAILS]

SELECT COALESCE(CAST(MAX([bwd_DateModified]) AS date), '2019-01-01') AS next_date
FROM [dbo].[BGT_BETWAYDETAILS]

第一个在每个年度表上运行时会立即运行。

第二个,似乎要花很长时间。这个查询计划看起来很奇怪。

enter image description here

该计划在每个年度表上显示两次索引扫描。

每个年度表的计划都是我期望看到的:

enter image description here

最后,非索引日期列上的计划也是我期望看到的(聚集索引扫描)。每个表上的聚集索引扫描。此查询运行时间约为 3 分钟,符合预期。

enter image description here

这里有什么问题吗?我缺少一些反模式吗?为什么非聚集索引上的索引扫描按照实时计划进行了2次?我希望 View 的响应速度与各个表的响应速度一样快。

郑重声明,我在 SQL Server 2017 上运行它。

最佳答案

这看起来像是一个优化器限制。我已提交 a suggestion that this should be improved .

一个更简单的例子是

CREATE TABLE T1(X INT NULL UNIQUE CLUSTERED);
CREATE TABLE T2(X INT NULL UNIQUE CLUSTERED);

INSERT INTO T1 
OUTPUT INSERTED.X INTO T2
SELECT TOP 100000 NULLIF(ROW_NUMBER() OVER (ORDER BY 1/0),1) 
FROM sys.all_objects o1, 
     sys.all_objects o2;

然后

WITH CTE AS
(
SELECT X FROM T1
UNION ALL
SELECT X FROM T2
)
SELECT MAX(X)
FROM CTE
OPTION (QUERYRULEOFF ScalarGbAggToTop)

这会禁用查询优化器规则ScalarGbAggToTop查询计划执行 MAX然后在每个单独的表上计算 MAX MAX的-es - 与

相同
SELECT MAX(MaxX)
FROM 
(
SELECT MAX(X) AS MaxX FROM T1 
UNION ALL
SELECT MAX(X) AS MaxX FROM T1 
) T

enter image description here

ScalarGbAggToTop启用规则的计划现在看起来像这样

enter image description here

它正在有效地执行以下操作...

SELECT MAX(MaxX)
FROM   (SELECT MAX(X) AS MaxX
        FROM   (SELECT TOP 1 X
                FROM   T1
                WHERE  X IS NULL
                UNION ALL
                SELECT TOP 1 X
                FROM   T1
                WHERE  X IS NOT NULL
                ORDER  BY X DESC) T1
        UNION ALL
        SELECT MAX(X) AS MaxX
        FROM   (SELECT TOP 1 X
                FROM   T2
                WHERE  X IS NULL
                UNION ALL
                SELECT TOP 1 X
                FROM   T2
                WHERE  X IS NOT NULL
                ORDER  BY X DESC) T2) T0 

...但是效率非常低。运行上面的 SQL 将给出一个带有查找的计划,并且每个分支仅读取一行。

ScalarGbAggToTop制作的计划仅对流聚合计划进行了最小的更改。看起来它从中进行扫描并对它应用向后排序,然后对 NOT NULL 使用向后排序。和NULL分支机构。并且不执行任何额外的探索来查看是否存在更高效的访问路径。

这意味着在病理情况下,所有行都是 NULLNOT NULL其中一项扫描最终将读取表中的所有行(如果适用于所有 4 个表,则在您的情况下为 50 亿行)。即使有 NULL 的混合和NOT NULL事实上IS NULL分支正在进行向后扫描不是最优的,因为 NULL在 SQL Server 中排在第一位,因此位于索引的开头。

添加 NOT NULL首先分支似乎基本上是不必要的,因为如果没有它,查询将返回相同的结果。我想它只是需要它知道是否显示消息

Warning: Null value is eliminated by an aggregate or other SET operation.

但我怀疑你关心这个。在这种情况下添加显式 WHERE ... NOT NULL解决了问题。

WITH CTE AS
(
SELECT X FROM T1
UNION ALL
SELECT X FROM T2
)
SELECT MAX(X)
FROM CTE
WHERE X IS NOT NULL
;

它现在正在寻找 NOT NULL索引的一部分并向后读取(从每个表读取第一行后停止)

enter image description here

关于sql-server - View 上最大(日期)查询的奇怪查询计划,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61957385/

相关文章:

sql - 相同 SQL 查询在一个数据库中运行的时间比在同一服务器下的另一个数据库中运行的时间长

Sql通配符: performance overhead?

sql - 通过外键从另一个表获取值

SQL Server : SUM Values and find the difference from two different tables

c# - 更新 SQL Server 时出错

sql - 解释计划在应用程序之间有何不同

sql - 无法使用 Windows 身份验证登录到 SQL Server

c# - 如何在 Visual Studio 2015 中为 SQL Server 创建数据库图表?

mysql - 如何提高MySQL存储过程运行20秒

MySQL 查询性能 - 时间上的巨大差异