Ace*_*ePL 6 performance sql-server sql-server-2012 query-performance
在我的查询中,我不确定如何解决某些问题。
一、定义:
快递服务表。有一个记录。
CREATE TABLE [dbo].[CS](
[ServiceID] [int] IDENTITY(1,1) NOT NULL,
[CSID] [nvarchar](6) NULL,
[CSDescription] [varchar](50) NULL,
[OperatingDays] [int] NULL,
[DefaultService] [bit] NULL,
CONSTRAINT [CourierServices_PK] PRIMARY KEY CLUSTERED
(
[ServiceID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90
) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[CS] ON
INSERT [dbo].[CS] ([ServiceID], [CSID], [OperatingDays], [DefaultService])
VALUES (1, N'RM48', 2, 1)
SET IDENTITY_INSERT [dbo].[CS] OFF
SET ANSI_PADDING ON
GO
/****** Object: Index [ix_CourierServices] Script Date: 19/04/2017 14:27:03 ******/
CREATE NONCLUSTERED INDEX [ix_CourierServices] ON [dbo].[CS]
(
[CSID] ASC,
[DefaultService] ASC,
[OperatingDays] ASC
)
INCLUDE ( [CSDescription]) 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]
GO
Run Code Online (Sandbox Code Playgroud)
日历数据库和表格,由Genius Jim Horn编写:
CREATE TABLE [dbo].[days](
[PKDate] [date] NOT NULL,
[calendar_year] [smallint] NULL,
[calendar_quarter] [tinyint] NULL,
[calendar_quarter_desc] [varchar](10) NULL,
[calendar_month] [tinyint] NULL,
[calendar_month_name_long] [varchar](30) NULL,
[calendar_month_name_short] [varchar](10) NULL,
[calendar_week_in_year] [tinyint] NULL,
[calendar_week_in_month] [tinyint] NULL,
[calendar_day_in_year] [smallint] NULL,
[calendar_day_in_week] [tinyint] NULL,
[calendar_day_in_month] [tinyint] NULL,
[dmy_name_long] [varchar](30) NULL,
[dmy_name_long_with_suffix] [varchar](30) NULL,
[day_name_long] [varchar](10) NULL,
[day_name_short] [varchar](10) NULL,
[continuous_year] [tinyint] NULL,
[continuous_quarter] [smallint] NULL,
[continuous_month] [smallint] NULL,
[continuous_week] [smallint] NULL,
[continuous_day] [int] NULL,
[description] [varchar](100) NULL,
[is_weekend] [tinyint] NULL,
[is_holiday] [tinyint] NULL,
[is_workday] [tinyint] NULL,
[is_event] [tinyint] NULL,
PRIMARY KEY CLUSTERED
(
[PKDate] 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
/****** Object: Index [ix_days] Script Date: 19/04/2017 14:38:47 ******/
CREATE NONCLUSTERED INDEX [ix_days] ON [dbo].[days]
(
[PKDate] ASC
)
INCLUDE ( [is_weekend],
[is_holiday],
[is_workday],
[is_event]) 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]
GO
Run Code Online (Sandbox Code Playgroud)
现在,我正在运行一个查询,它根据代码位引用两个表:
Select
OID
,case when
Cast(o.[CreationDate] as time) > '16:00:00'
then (select top 1 [PKDate] from [calendar].[dbo].days
where is_weekend <> 1 and is_holiday <>1 and
PKDate > cast(o.[CreationDate] as date)
order by PKDate asc)
else (select top 1 [PKDate] from [calendar].[dbo].days
where is_weekend <> 1 and is_holiday <>1 and
PKDate >= Cast(o.[CreationDate] as date)
order by PKDate asc)
end OperatingDate
,case when
Cast(o.[CreationDate] as time) > '16:00:00'
then (select top 1 [PKDate] from [calendar].[dbo].days
where is_weekend <> 1 and is_holiday <>1 and
PKDate > dateadd(day,isnull(
(select top 1 [operatingdays]
from [dbo].[CS]
where DefaultService = 1)
,2)+1,Cast(o.[CreationDate] as date))
order by PKDate asc)
else (select top 1 [PKDate] from [calendar].[dbo].days
where is_weekend <> 1 and is_holiday <>1 and
PKDate > dateadd(day,isnull(
(select top 1 [operatingdays]
from [dbo].[CS]
where DefaultService = 1)
,2), Cast(o.[CreationDate] as date))
order by PKDate asc)
end EstimatedDeliveryDate
,(select dateadd(day,3,o.[CreationDate])) DeliveryDate
From o
Run Code Online (Sandbox Code Playgroud)
现在的问题是,与索引扫描和执行次数有关:为什么是 20 亿?还是60亿?诚然,整个查询的输出是 170 万行,但这并不能解释查询计划中显示的疯狂数字:
https://www.brentozar.com/pastetheplan/?id=H1iahxHAe
如果我可以将所有这些扫描打平,我可以显着减少查询时间,但首先:我如何解释这些数字以找到解决方案?
天表包含 7.6k 行(涵盖 2000-2020 年)。
让我们从计划的右上角开始。那部分计算OperatingDate列:
由于我们为外部行集取回了 1.72 M 行,我们可以预期大约 1.72 M 行索引针对ix_days。确实是这样。有 478k 行,o.[CreationDate] as time) > '16:00:00'因此该CASE语句将 478k 搜索发送到一个分支,其余的发送到另一个。
请注意,您拥有的索引对于此查询来说并不是最有效的索引。我们只能对 做一个搜索谓词PKDate。其余过滤器用作谓词。这意味着在找到匹配项之前,查找可能会遍历多行。我假设您日历表中的大多数日子不是周末或假期,因此对于此查询可能没有实际影响。但是,您可以在 上定义索引is_weekend, is_holiday, PKDate。这应该让你立即寻找你想要的第一行。
为了更清楚地说明这一点,让我们来看一个简单的例子:
-- does a scan
SELECT TOP 1 PkDate
FROM [Days]
WHERE is_weekend <> 1 AND is_holiday <> 1
AND PkDate >= '2000-04-01'
ORDER BY PkDate;
-- does a seek, reads 3 rows to return 1
SELECT TOP 1 PkDate
FROM [Days]
WHERE is_weekend = 0 AND is_holiday = 0
AND PkDate >= '2000-04-01'
ORDER BY PkDate;
-- create new index
CREATE NONCLUSTERED INDEX [ix_days_2] ON [dbo].[days]
(
[is_weekend],
[is_holiday],
PkDate
)
-- does a seek, reads 1 row to return 1
SELECT TOP 1 PkDate
FROM [Days]
WHERE is_weekend = 0 AND is_holiday = 0
AND PkDate >= '2000-04-01'
ORDER BY PkDate;
DROP INDEX [days].[ix_days_2];
Run Code Online (Sandbox Code Playgroud)
让我们进入更有趣的部分,即计算DeliveryDate列的分支。我只会包括其中的一半:
我怀疑您希望优化器做的是将其计算为标量:
dateadd(day,isnull(
(select top 1 [operatingdays]
from [dbo].[CS]
where DefaultService = 1)
,2)+1,Cast(o.[CreationDate] as date))
Run Code Online (Sandbox Code Playgroud)
并使用它的值来使用ix_days. 不幸的是,优化器不会这样做。相反,它对索引应用行目标并进行扫描。对于从扫描返回的每一行,它会检查该值是否与针对 的过滤器匹配[dbo].[CS]。一旦找到匹配的一行,扫描就会停止。SQL Server 估计它平均只会从扫描中拉回 3.33 行,直到找到匹配项。如果这是真的,那么您将看到大约 150 万次针对[dbo].[CS]. 相反,优化器对表执行了 20 亿次,因此估计值相差 1000 多倍。
作为一般规则,您应该仔细检查嵌套循环内侧的任何扫描。当然,有些查询正是您想要的。并且仅仅因为您有一个搜索并不意味着查询将是有效的。例如,如果查找返回许多行,则可能与执行扫描没有太大区别。你没有在这里发布完整的查询,但我会讨论一些可能有帮助的想法。
这个查询有点奇怪:
select top 1 [operatingdays]
from [dbo].[CS]
where DefaultService = 1
Run Code Online (Sandbox Code Playgroud)
它是不确定的,因为您TOP没有ORDER BY. 但是,表本身有 1 行,您总是从o. 如果可能,我会尝试将这个查询的值保存到一个局部变量中,然后在查询中使用它。这应该再次为您节省总共 80 亿次扫描[dbo].[CS],我希望看到索引搜索而不是对ix_days. 我能够在我的机器上模拟一些数据。这是查询计划的一部分:
现在我们有了所有的搜索,这些搜索不应该处理太多额外的行。但是,真正的查询可能比这更复杂,因此您可能无法使用变量。
假设我编写了一个不使用TOP. 相反,我将使用MIN. SQL Server 能够以更有效的方式处理该子查询。TOP 可以阻止某些查询转换。这是我的子查询:
WHERE PKDate > dateadd(day,isnull(
(select MIN([operatingdays])
from [dbo].[CS]
where DefaultService = 1)
,2), Cast(o.[CreationDate] as date))
Run Code Online (Sandbox Code Playgroud)
该计划可能如下所示:
现在我们将只对CS表格进行大约 150 万次扫描。我们还针对ix_days能够使用子查询结果的索引获得了更有效的索引查找:
当然,我并不是说你应该重写你的代码来使用它。它可能会返回不正确的结果。重要的一点是您可以使用子查询获得所需的索引查找。您只需要以正确的方式编写子查询。
再举一个例子,让我们假设您绝对需要TOP在子查询中保留运算符。可以添加一个冗余过滤器PkDate来获得更好的性能。我将假设子查询的结果是非负的并且很小。这意味着这个查询将是等价的:
PKDate > Cast(o.[CreationDate] as date) AND
PKDate > dateadd(day,isnull(
(select top 1 [operatingdays]
from [dbo].[CS]
where DefaultService = 1)
,2)+1,Cast(o.[CreationDate] as date))
Run Code Online (Sandbox Code Playgroud)
这改变了使用寻求的计划:
重要的是要意识到搜索可能只返回一行。重要的一点是 SQL Server 可以从o.[CreationDate]. 如果日期之间存在很大差距,那么索引查找将处理许多额外的行,并且查询效率将降低。
| 归档时间: |
|
| 查看次数: |
1128 次 |
| 最近记录: |