模拟真实数据。生成大表(最终表的大小应与您在现实生活中的预期相同),并按照您在现实生活中的预期分配产品和日期。从添加三个独立的产品索引开始,开始日期、结束日期。尝试运行查询。分析执行计划。尝试其他索引组合。比较计划和绩效。如果没有提供可接受的性能,请在此处返回生成示例数据和查询的脚本。
在我的测试中,优化器是三个独立索引搜索的内部连接结果。
创建表
每列加上三个独立的索引:
CREATE TABLE [dbo].[Test](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ProductID] [int] NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_EndDate] ON [dbo].[Test]
(
[EndDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_ProductID] ON [dbo].[Test]
(
[ProductID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_StartDate] ON [dbo].[Test]
(
[StartDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
生成测试数据
- 总共 1M 行。
- 多达 100 个不同的产品 ID,分布均匀。
- 开始日期在 2000 年 1 月 1 日后的 10,000 天内(约 27 年的时间跨度)
- 结束日期在开始日期后的 1000 天内(最长持续约 3 年)
查询:
INSERT INTO Test(ProductID, StartDate, EndDate)
SELECT TOP(1000000)
CA.ProductID
,DATEADD(day, StartOffset, '2000-01-01') AS StartDate
,DATEADD(day, StartOffset+DurationDays, '2000-01-01') AS EndDate
FROM
sys.all_objects AS o1
cross join sys.all_objects AS o2
cross apply
(
SELECT
cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 100 + 1 as int) AS ProductID
,cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 10000 as int) AS StartOffset
,cast((cast(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 1000 as int) AS DurationDays
) AS CA
要优化的查询:
DECLARE @VarDate date = '2004-01-01';
SELECT *
FROM Test
WHERE
ProductID = 1
AND @VarDate >= StartDate
AND @VarDate <= EndDate
;
它返回约 500 行。
执行计划
服务器建议如下索引:
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Test] ([ProductID],[StartDate],[EndDate])
INCLUDE ([ID])
但是拥有这样的索引是愚蠢的,恕我直言。
如果您总共有 100 万行和 100K 个不同的产品 ID,而不是 100 个;换句话说,如果按特定产品 ID 搜索会消除绝大多数行,那么最好的选择可能是在 ProductID 上有一个索引并将其他列包含在其中:
CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID])
INCLUDE ([StartDate],[EndDate])
或
CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID], [StartDate])
INCLUDE ([EndDate])
或
CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID],[EndDate])
INCLUDE ([StartDate])
如果其中一个日期具有良好的选择性,则在其上使用索引而不是 ProductID。
如果没有一个列具有良好的选择性,那就很难了。
编辑
盲目地按照优化器的建议创建索引是愚蠢的,因为您知道您将搜索特定的 ProductID,然后搜索 StartDates 范围,然后搜索 EndDates 范围。因此,第三列 EndDate 将永远不会用于搜索本身。在这种情况下,最好将INCLUDE 列在索引中,而不是让它成为索引的一部分,如上所示。
如果查询是针对特定 ProductID 和 particular StartDate(不是范围),然后针对 EndDate 范围(或特定 EndDate),那么将 EndDate 作为索引的一部分会有所帮助.