通常,这是一个很难解决的问题,但是我们可以做一些事情来帮助优化器选择计划。该脚本创建一个具有10,000行的表,并具有行的已知伪随机分布,以说明:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
第一个问题是如何索引该表。一种选择是在DATETIME
列上提供两个索引,因此优化器至少可以选择是在上搜索StartDate
还是在上搜索EndDate
。
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
自然地,两者StartDate
和的不等式EndDate
意味着每个索引中只有一列可以支持示例查询中的查找,但这是我们所能做的最好的事情。我们可能考虑将每个索引中的第二列INCLUDE
而不是键,但是我们可能还有其他查询,它们可以对前导列执行相等查找,而对第二列执行不相等查找。此外,我们可以通过这种方式获得更好的统计信息。无论如何...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
该查询使用变量,因此通常,优化器将猜测选择性和分布,从而得出81行的基数估计值。实际上,查询产生2076行,这一差异在更复杂的示例中可能很重要。
在SQL Server 2008 SP1 CU5或更高版本(或R2 RTM CU1)上,我们可以简单地通过添加到上面的查询中来利用参数嵌入优化来获得更好的估计。这将导致在批处理执行之前进行编译,从而使SQL Server可以“查看”实际参数值并对其进行优化。进行此更改后,估算值将提高到468行(尽管您确实需要检查运行时计划才能看到此行)。此估算值比81行要好,但仍不能完全关闭。在某些情况下,由跟踪标志2301启用的建模扩展可能会有所帮助,但此查询不能。OPTION (RECOMPILE)
SELECT
问题是两个范围搜索限定的行重叠。优化程序的成本核算和基数估计组件中进行的简化假设之一是谓词是独立的(因此,如果两个谓词的选择性均为50%,则应用这两个谓词的结果将限定50%的行等于50%的行) )。在这种相关性存在问题的地方,我们通常可以使用多列和/或过滤后的统计数据来解决它。在两个范围的起点和终点未知的情况下,这变得不切实际。这是我们有时不得不诉诸于将查询重写为恰好产生更好估计的形式的地方:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
这种形式碰巧会产生2110行的运行时估计(相对于实际的2076行)。除非您使用TF 2301,否则这种情况下,更高级的建模技术将使您理解并获得与以前完全相同的估计:468行。
有一天,SQL Server可能会获得对间隔的本地支持。如果这提供了良好的统计支持,则开发人员可能会像这样少调整一下查询计划。