我有一个包含4个年表的视图:
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]
第一个,在每个年度表上运行时,立即运行。
第二个,似乎要永远坚持下去。此查询计划似乎很奇怪。
该计划在每个年表上显示两次索引扫描。
每个年度表的计划是我希望看到的:
最后,非索引日期列上的计划也是我希望看到的(聚集索引扫描)。对每个表进行聚集索引扫描。此查询将在大约3分钟内运行。
这里是什么问题?我缺少一些反模式吗?为什么根据实时计划对非聚集索引进行了两次索引扫描?我希望该视图能够像单个表一样快速地响应。
出于记录,我正在SQL Server 2017上运行它。
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
-es的MAX
-等同于
SELECT MAX(MaxX)
FROM
(
SELECT MAX(X) AS MaxX FROM T1
UNION ALL
SELECT MAX(X) AS MaxX FROM T1
) T
启用ScalarGbAggToTop
规则后,该计划现在看起来像这样正在有效地执行以下操作...
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
分支都使用向后排序。并且不执行任何其他探索以查看是否存在更有效的访问路径。这意味着在所有行均为
NULL
或NOT NULL
的病理情况下,一次扫描最终将读取表中的所有行(在您的情况下为30亿)。即使NULL
和NOT NULL
混合在一起,IS NULL
分支正在执行向后扫描的事实也是次优的,因为NULL
在SQL Server中首先排序,因此将在索引的开头。首先添加
NOT NULL
分支似乎不被保证,因为如果没有它,查询将返回相同的结果。添加显式
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
部分中进行查找并向后读(在从每个表中读取第一行之后停止)
语义在最终结果方面是相同的。行为上有一个非常细微的差别,即您将不再看到消息
警告:通过合计或其他SET消除了空值操作。
但是我怀疑有人在乎这个。