Eri*_*ing 25 performance index sql-server query-performance
对我来说,我有一个关于 SARGability 的有趣问题。在这种情况下,它是关于对两个日期列之间的差异使用谓词。这是设置:
USE [tempdb]
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#sargme') IS NOT NULL
BEGIN
DROP TABLE #sargme
END
SELECT TOP 1000
IDENTITY (BIGINT, 1,1) AS ID,
CAST(DATEADD(DAY, [m].[severity] * -1, GETDATE()) AS DATE) AS [DateCol1],
CAST(DATEADD(DAY, [m].[severity], GETDATE()) AS DATE) AS [DateCol2]
INTO #sargme
FROM sys.[messages] AS [m]
ALTER TABLE [#sargme] ADD CONSTRAINT [pk_whatever] PRIMARY KEY CLUSTERED ([ID])
CREATE NONCLUSTERED INDEX [ix_dates] ON [#sargme] ([DateCol1], [DateCol2])
Run Code Online (Sandbox Code Playgroud)
我会经常看到的是这样的:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
Run Code Online (Sandbox Code Playgroud)
...这绝对不是SARGable。它导致索引扫描,读取所有 1000 行,不好。估计行很臭。你永远不会把它投入生产。
如果我们能够实现 CTE,那就太好了,因为从技术上讲,这将帮助我们使这个,好吧,更 SARGable-er。但是不,我们得到了与 top 相同的执行计划。
/*would be nice if it were sargable*/
WITH [x] AS ( SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) AS [ddif]
FROM
[#sargme] AS [s])
SELECT
*
FROM
[x]
WHERE
[x].[ddif] >= 48;
Run Code Online (Sandbox Code Playgroud)
当然,因为我们没有使用常量,所以这段代码没有任何改变,甚至不是半个 SARGable。没有乐趣。相同的执行计划。
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Run Code Online (Sandbox Code Playgroud)
如果您感到幸运,并且您遵守了连接字符串中的所有 ANSI SET 选项,您可以添加一个计算列,并对其进行搜索...
ALTER TABLE [#sargme] ADD [ddiff] AS
DATEDIFF(DAY, DateCol1, DateCol2) PERSISTED
CREATE NONCLUSTERED INDEX [ix_dates2] ON [#sargme] ([ddiff], [DateCol1], [DateCol2])
SELECT [s].[ID] ,
[s].[DateCol1] ,
[s].[DateCol2]
FROM [#sargme] AS [s]
WHERE [ddiff] >= 48
Run Code Online (Sandbox Code Playgroud)
这将为您提供三个查询的索引查找。奇怪的人是我们将 48 天添加到 DateCol1 的地方。与查询DATEDIFF中WHERE子句,CTE以计算列上的谓词,并最终查询全部给你好得多估计更加美好的计划,以及所有。
这让我想到了一个问题:在单个查询中,是否有一种 SARGable 方式来执行此搜索?
没有临时表,没有表变量,没有改变表结构,也没有视图。
我可以使用自联接、CTE、子查询或多次传递数据。可以使用任何版本的 SQL Server。
避免计算列是一个人为的限制,因为我对查询解决方案比其他任何事情都更感兴趣。
Pau*_*ite 17
只需快速添加它,使其作为答案存在(尽管我知道这不是您想要的答案)。
一个索引计算列通常是这种类型的问题的解决方案。
它:
要清楚最后一点,在这种情况下不需要保留计算列:
-- Note: not PERSISTED, metadata change only
ALTER TABLE #sargme
ADD DayDiff AS DATEDIFF(DAY, DateCol1, DateCol2);
-- Index the expression
CREATE NONCLUSTERED INDEX index_name
ON #sargme (DayDiff)
INCLUDE (DateCol1, DateCol2);
Run Code Online (Sandbox Code Playgroud)
现在查询:
SELECT
S.ID,
S.DateCol1,
S.DateCol2,
DATEDIFF(DAY, S.DateCol1, S.DateCol2)
FROM
#sargme AS S
WHERE
DATEDIFF(DAY, S.DateCol1, S.DateCol2) >= 48;
Run Code Online (Sandbox Code Playgroud)
...给出了以下微不足道的计划:
正如 Martin Smith 所说,如果您有使用错误设置选项的连接,您可以创建一个常规列并使用触发器维护计算值。
当然,正如 Aaron 在他的回答中所说,所有这些只有在有真正的问题需要解决时才真正重要(代码挑战除外)。
考虑到这一点很有趣,但是鉴于问题中的限制,我不知道有什么方法可以合理地实现您想要的目标。似乎任何最佳解决方案都需要某种类型的新数据结构;我们最接近的是由上述非持久计算列上的索引提供的“函数索引”近似值。
Dan*_*her 13
冒着受到 SQL Server 社区中一些知名人士的嘲笑的风险,我要伸出脖子说,不。
为了使您的查询成为 SARGable,您必须基本上构建一个查询,该查询可以在索引的一系列连续行中查明起始行。使用 index ix_dates,行不会按DateCol1和之间的日期差异排序DateCol2,因此您的目标行可以分布在索引中的任何位置。
自联接、多次传递等都有一个共同点,即它们至少包括一次索引扫描,尽管(嵌套循环)联接很可能使用索引查找。但我看不出如何消除扫描。
至于获得更准确的行估计,没有关于日期差异的统计。
以下相当丑陋的递归 CTE 构造在技术上确实消除了对整个表的扫描,尽管它引入了嵌套循环连接和(可能非常大)数量的索引查找。
DECLARE @from date, @count int;
SELECT TOP 1 @from=DateCol1 FROM #sargme ORDER BY DateCol1;
SELECT TOP 1 @count=DATEDIFF(day, @from, DateCol1) FROM #sargme WHERE DateCol1<=DATEADD(day, -48, {d '9999-12-31'}) ORDER BY DateCol1 DESC;
WITH cte AS (
SELECT 0 AS i UNION ALL
SELECT i+1 FROM cte WHERE i<@count)
SELECT b.*
FROM cte AS a
INNER JOIN #sargme AS b ON
b.DateCol1=DATEADD(day, a.i, @from) AND
b.DateCol2>=DATEADD(day, 48+a.i, @from)
OPTION (MAXRECURSION 0);
Run Code Online (Sandbox Code Playgroud)
它创建一个包含每次一个索引后台DateCol1在表中,然后执行索引查找(范围扫描)对每个那些的DateCol1和DateCol2是至少48天前进。
更多的 IO,稍长的执行时间,行估计仍然遥遥无期,并且由于递归而并行化的机会为零:我猜这个查询可能有用,如果您在相对较少的不同的连续值中有非常多的值DateCol1(保持搜索次数减少)。
我尝试了一堆古怪的变体,但没有找到比你更好的版本。主要问题是您的索引在 date1 和 date2 如何排序方面看起来像这样。第一列将在一个很好的搁置线上,而它们之间的间隙将非常参差不齐。您希望它看起来更像一个漏斗,而不是它真正的样子:
Date1 Date2
----- -------
* *
* *
* *
* *
* *
* *
* *
* *
Run Code Online (Sandbox Code Playgroud)
我真的没有任何办法可以使两点之间的某个增量(或增量范围)可查找。我的意思是执行一次 + 范围扫描的单个搜索,而不是为每一行执行的搜索。这将在某个时候涉及扫描和/或排序,而这些显然是您想要避免的。太糟糕了,您不能在过滤索引中使用DATEADD/ 之类的表达式,也不能DATEDIFF执行任何可能的架构修改,以允许对日期差异的乘积进行排序(例如在插入/更新时计算增量)。照原样,这似乎是扫描实际上是最佳检索方法的情况之一。
你说这个查询不好玩,但如果你仔细看,这是迄今为止最好的查询(如果你忽略计算标量输出会更好):
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Run Code Online (Sandbox Code Playgroud)
原因是,DATEDIFF与仅针对索引中的非前导键列进行计算相比,避免潜在地减少了一些 CPU ,并且还避免了一些令人讨厌的隐式转换datetimeoffset(7)(不要问我为什么会有这些,但它们确实存在)。这是DATEDIFF版本:
<Predicate>
<ScalarOperator ScalarString="datediff(day,CONVERT_IMPLICIT(datetimeoffset(7),[splunge].[dbo].[sargme].[DateCol1] as [s].[DateCol1],0),CONVERT_IMPLICIT(datetimeoffset( 7),[splunge].[dbo].[sargme].[DateCol2] 为 [s].[DateCol2],0))>=(48)">
这是没有的DATEDIFF:
<Predicate>
<ScalarOperator ScalarString="[splunge].[dbo].[sargme].[DateCol2] as [s].[DateCol2]>=dateadd(day,(48),[splunge].[dbo].[ sargme].[DateCol1] 作为 [s].[DateCol1])">
此外,当我将索引更改为仅包含 时,我发现在持续时间方面的结果稍好一些DateCol2(并且当两个索引都存在时,SQL Server 总是选择一个键和一个包含列与多键的索引)。对于这个查询,因为无论如何我们都必须扫描所有行才能找到范围,所以将第二个日期列作为键的一部分并以任何方式排序都没有好处。虽然我知道我们在这里无法进行搜索,但通过强制针对前导键列进行计算,并且仅针对次要或包含的列执行计算,不会妨碍获得搜索的能力,这本身就是一种良好的感觉。
如果是我,并且我放弃了寻找可解决的解决方案,我知道我会选择哪一个 - 使 SQL Server 完成最少工作量的那个(即使增量几乎不存在)。或者更好的是,我会放宽我对架构更改等的限制。
所有这些有多重要?我不知道。我在表中创建了 1000 万行,并且所有上述查询变化仍然在不到一秒钟的时间内完成。这是在笔记本电脑上的虚拟机上(当然,带有 SSD)。
| 归档时间: |
|
| 查看次数: |
3116 次 |
| 最近记录: |