【问题标题】:Is a low-selectivity covering index helpful when you want to select large sections of data in a large table?当您想在大表中选择大量数据时,低选择性覆盖索引是否有用?
【发布时间】:2013-11-08 17:52:44
【问题描述】:

运行于: SQL Server 2008 R2 标准。虽然我认为这是所有数据库的问题,而不仅仅是 SQL Server。

背景:我一直听说/读过/被告知,索引的前沿应该是高度选择性的。当您有查询寻求特定值或一小组值(产品 ID 或类似的东西)时,这是有意义的。

一般性问题:高选择性索引是否有用?

例如:我有一个包含 3.5 亿行的表。该表包含一堆价格。该表包含以下列:

  • priceId -- 表上的聚集索引
  • warehouseId -- fk 到 10 个仓库之一,平均分布在 150m 行
  • algorithmId -- fk 到我计算价格的 23 种算法之一,平均分布在 1.5 亿行中
  • priceDate -- 我们上次计算价格的日期
  • productId

然后我运行这个查询:

select productId 
from price 
where warehouseId = 1 
    and algorithmId = 1 
order by priceDate

具体问题:我不会从这样的索引中受益吗?

create nonclustered index ix_p 
on price (warehouseId, algorithmId, priceDate) includes (productId)

看来我会受益 b/c 我已经创建了一个覆盖索引,过滤列组织得很好,这样 SQL Server 可以一次切出大块并按priceDate 排序。那有意义吗?它有效吗?

注意:我会试试这个,然后告诉你我发现了什么。

【问题讨论】:

  • 索引策略几乎总是取决于您正在运行的查询。典型的观点是,如果您从表中选择超过 10% 的数据,则应该使用全扫描或分区而不是索引来访问该数据,因为索引需要两次磁盘读取 - 一次读取索引,另一个读取表上的数据...
  • 谢谢。我已经稍微编辑了我的问题 b/c 我的意思是要明确这是一个覆盖非聚集索引,所以不需要回到表中。所有字段都可用作键列或包含列。
  • 我考虑过分区,但我运行的是 2008 标准,我需要升级到企业才能获得分区功能。
  • 您不必分区以将数据存储在多个驱动器上,也不必单独存储索引。如果索引当前存储在主索引上,您可能需要为索引创建一个文件组。
  • 我的索引在一个单独的文件组中。该文件组有一个专用驱动器,与主驱动器或日志记录分开。我现在没有另一个驱动器可以跨越。这不是我要问的问题,真的。

标签: sql sql-server database indexing


【解决方案1】:

简短的回答 - 是的,但您的存储空间基本上翻了一番。

长答案:

我在具有 1.5 亿行数据的 SQL 2012 VirtualBox Server 2008 VM 上对此进行了测试。文件组存储在 VM 映像上,该映像位于与固态驱动器的 USB 3.0 连接上(顺序读取似乎约为 250 mb/s,写入约为 150 mb/s)。

我用伪随机日期和产品 ID 构建了一个表,其中 1-10 的仓库 ID 均匀分布,1-23 的算法均匀分布。 (基本上我在 SSIS 中编写了一个加载数据的源脚本组件)。

表存储空间约为 4.7 GB,主键 priceid 上有一个聚集索引。

运行此查询:

select productId 
from price 
where warehouseId = 1 
    and algorithmId = 1 
order by priceDate

在大约 30 秒内返回了大约 100 万行。 Plan 表示聚集索引扫描加上排序(按 priceDate 排序)。

然后我添加了这个非聚集索引:

create nonclustered index ix_p 
on price (warehouseId, algorithmId, priceDate) include (productId)

这个索引几乎和表一样大 - 大约 4.3 GB。

添加非聚集索引消除了 priceDate 上的 SORT 步骤,并改为执行非聚集索引查找以访问数据。创建此索引需要 11 多分钟。

相同的查询: 在大约 4 秒内返回了大约 100 万行。 Plan 表示非聚集索引查找。

我认为这样做最重要的事情本质上是创建数据的两份副本——一份在聚集索引结构中,另一份在“非聚集”结构中。

我预计插入需要大约两倍的时间,因为现在您必须为每个插入创建基本上两行。

您是否定期更新此表?可能还有其他一些可能会有所帮助的策略。

【讨论】:

    【解决方案2】:

    我刚刚完成了一个类似于我在问题中描述的非聚集索引。表有 101,308,183 行,每行 61 个字节。以下是一些结果:

    使用当前的“选择性”索引,以 productId 和仓库为键:

    • 返回 461,000 行
    • 平均运行时间:2 分 36 秒
    • 扫描次数:116
    • 逻辑读取:9,870,354
    • 物理读取:20,086
    • 预读:967,324

    使用新的非选择性索引,如我原来的问题中所述:

    • 返回 461,000 行
    • 平均运行时间:47 秒
    • 扫描次数:76
    • 逻辑读取:109,934
    • 物理读取:0
    • 预读读数:1

    总而言之,非选择性索引使我 逻辑读取次数减少了 90 倍(987 万到 110k),物理读取减少了 100%(从 20k 到0) 和 100% 的预读减少(从 967k 减少到 0)。

    再次,我相信这是因为 SQL 已经对所有数据进行了排序,因此很容易切割(即排除)大块数据。因为索引涵盖了这个查询(这是我们在生产环境中对其运行的仅有的两个查询之一),所以我们不会浪费时间进行键查找。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-12-31
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多