TSQL中具有多维度的类型2有效日期指数性能

时间:2015-05-15 19:36:38

标签: sql-server tsql indexing

复合指数如何在有效的日期表中起作用?

利用T-SQL,我想说我有一个表格,其有效日期与EffectiveStartDate和EffectiveEndDate相关联,以记录历史价格波动,因此我的表格将采用以下形式:

MyTable:=(EffStartDate日期,EffEndDate日期,ProductID int,ProductPrice钱) 其中EffEndDate =' 12/31/9999'当记录目前有效时。

让我们进一步假设我在表格中以下列形式实现两个索引: 聚集在(EffEndDate,EffStartDate,ProductID) 非聚集(EffEndDate,ProductID)

根据我的理解,聚集索引的索引创建将信息存储在由索引创建语句的列规范顺序排序的B树(可能是B +)中。因此,我设想在EffEndDate上进行表格排序,然后是EffStartDate,然后是ProductID。大多数情况下,我想使用类似于此的查询从历史中查询此表: 选择 * 来自MyTable 其中ProductID = @ProductID 和EffStartDate和EffEndDate之间的@MyDate。

我试图想象B树如何实际存储与这三列相关的信息。它是否将它存储为您在Python中可以找到的元组对象,还是在索引为复合时为B树添加更多维度?例如,对于给定的EffEndDate,B树是否有多个与EffStartDates相关的拆分树,然后是多个与ProductID相关的拆分树,或者每个拆分树是否基于元组进行拆分?这种反应似乎相信采用元组方法: Question

如果采用单维方法,我发现很难概念化这些类型的索引如何为两列之间的日期范围查找提供整体价值。对于实例,我认为这是正确的,给定一个日期(@MyDate),我们可以使用索引的EffEndDate组件将我们的搜索限制为仅EffEndDates> = @MyDate,然后使用EffStartDate组件来限制我们的搜索仅限EffStartDate< = @MyDate,然后在此剩余范围内搜索ProductID。这是指数的使用方式吗?

我预见到的问题是,如果我们有大约10万个产品每周非均匀地更新,我们最终会利用这个聚集索引生成一组巨大的有效日期范围,然后必须搜索每个日期范围以查找所需ProductID的实例。是否有更好的索引可以在这种类型的查询上实现?

我认为存在非聚集索引以快速搜索当前的ProductID价格,因为我们只需要两个拼图,因为EffEndDate将被设置为' 12/31/9999'。

或者,有没有办法实现跨越两列的多维索引以提高T-SQL中的查询性能?

谢谢!

3 个答案:

答案 0 :(得分:3)

这是一个真正需要2D或空间索引的应用程序,因为您正在有效地组合两个单独的不等式搜索。在不将表格卡入可以使用SQL Server空间索引的表单的情况下,您的选项是有限的。

如果可能,最好的方法是在EffStartDate和EffEndDate之间找到某种业务关系。例如,如果存在一个规则,即这些值不能比一年更远,那么这可以编码到WHERE子句中,以便为您可能需要进行大扫描的索引提供额外的选择性。

类似的东西:

SELECT *
FROM Table
WHERE @date BETWEEN EffStartDate and EffEndDate
    AND DATEADD(year, -1, @date) < EffStartDate

...您要添加其他业务约束以减少查询需要遍历的搜索空间。

您可能感兴趣的两篇文章是:

Quassnoi's answer to a similar question,讨论如何将这种类型的数据强制拟合为可以空间索引的格式,并且还有一个链接到他的博客,详细说明了可用于加速的递归CTE方法这些类型的查询没有架构修改。

Michael Asher's article使用业务知识提高相似类型查询的效果。

答案 1 :(得分:1)

表中没有贷款ID 我假设你的意思是ProductID

如果您要搜索ProductID = @ProductID,那么为什么在世界上您会将其作为复合索引的尾部进行搜索。为什么你最后会做简单的事情?

一周100K更新什么都没有。你在想这个。只需在每列上放一个索引,让查询优化器完成它的工作。

如果您在综合索引上设置了ProductID,EffStartDate日期,EffEndDate日期。
你没有比索引寻求更好! enter image description here

答案 2 :(得分:1)

模拟真实数据。生成大表(最终表的大小应与您在现实生活中的预期相同),并按照您在现实生活中的预期分配产品和日期。首先在产品,开始日期,结束日期上添加三个独立的独立索引。尝试运行查询。分析执行计划。尝试其他索引组合。比较计划和表现。如果没有提供可接受的性能,请返回此处,其中包含生成示例数据和查询的脚本。

在我的测试中,优化器是内部连接三个独立索引搜索的结果。

创建表格

每列加上三个独立索引:

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 - 01 - 01年(〜27年时间跨度)的10,000天内
  • 结束日期是从开始日期起的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行。

执行计划

plan

服务器建议使用以下索引:

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Test] ([ProductID],[StartDate],[EndDate])
INCLUDE ([ID])

但是这样的指数很愚蠢,恕我直言。

如果总共有1M行和100K不同的产品ID,而不是100;换句话说,如果按特定产品ID进行搜索会消除绝大多数行,那么最佳选项可能是在ProductID上有一个索引,并在其中包含其他列:

CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID])
INCLUDE ([StartDate],[EndDate])

OR

CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID], [StartDate])
INCLUDE ([EndDate])

OR

CREATE NONCLUSTERED INDEX IX_Product
ON [dbo].[Test] ([ProductID],[EndDate])
INCLUDE ([StartDate])

如果其中一个日期具有良好的选择性,则在其上设置索引而不是ProductID。

如果没有一个色谱柱具有良好的选择性,那么它很难。

修改

根据优化器的建议盲目地制作索引是愚蠢的,因为您知道将搜索特定的ProductID,然后搜索一系列StartDates然后搜索EndDates范围。因此,第三列EndDate永远不会用于搜索本身。在这种情况下,最好在索引中使用INCLUDE这一列,而不是将其作为索引的一部分,如上所示。

如果查询是针对特定ProductID而针对特定 StartDate(不是范围),然后针对一系列EndDate(或特定EndDate),那么将EndDate作为索引的一部分将有助于