【问题标题】:Should I index a bit field in SQL Server?我应该在 SQL Server 中索引一个位字段吗?
【发布时间】:2010-09-18 20:52:25
【问题描述】:

我记得有一次读到,索引具有低基数(少量不同值)的字段并不值得做。我承认我对索引的工作原理知之甚少,无法理解为什么会这样。

如果我有一个包含 1 亿行的表,并且我正在选择位字段为 1 的记录,该怎么办?假设在任何时间点,只有少数记录位字段为 1(而不是 0)。是否值得索引该位字段?为什么?

当然我可以测试它并检查执行计划,我会这样做,但我也很好奇它背后的理论。基数什么时候重要,什么时候不重要?

【问题讨论】:

  • 这是一个常见的查询吗?在查找“少数”记录时可能值得这样做,但在其他行上对您没有太大帮助。还有其他方法可以识别数据吗?
  • 虽然我不认为我会单独索引一个位列,但将位列作为复合索引的一部分包含在内是很常见的。当您的应用程序几乎总是在寻找活跃的客户时,一个简单的例子是 ACTIVE 的索引,LASTNAME 而不仅仅是姓氏。
  • “我记得有一次读到,索引具有低基数(不同值的数量很少)的字段并不真正值得做”那是因为 SQL Server 几乎总是会发现这样做更有效率表扫描比读取索引。所以基本上你的索引永远不会被使用,维护它是一种浪费。正如其他人所说,在复合索引中可能没问题。
  • 我不同意。如果您的分布是 50/50,那么您永远不会使用索引,因为执行表扫描会更快。但是,如果你只有5个、1个值、100万个0个值,那么在搜索1的时候很有可能会用到索引。
  • 在您给出的示例中,我更倾向于将姓氏放在首位。这取决于具体的查询工作负载,但一般来说,优先选择更具选择性的列意味着更有可能使用索引。

标签: sql-server indexing


【解决方案1】:

考虑一下 SQL 中的索引是什么——索引实际上是一块内存,指向其他内存块(即指向行的指针)。索引被分成页面,以便可以根据使用情况从内存中加载和卸载索引的某些部分。

当您请求一组行时,SQL 使用索引来查找行比表扫描(查看每一行)更快。

SQL 具有聚集索引和非聚集索引。我对聚集索引的理解是它们将相似的索引值分组到同一个页面中。这样,当您请求与索引值匹配的所有行时,SQL 可以从聚集的内存页中返回这些行。这就是为什么尝试对 GUID 列进行群集索引是一个坏主意的原因 - 您不要尝试对随机值进行群集。

当您索引一个整数列时,SQL 的索引包含一组针对每个索引值的行。如果您有 1 到 10 的范围,那么您将有 10 个索引指针。根据有多少行,可以对它进行不同的分页。如果您的查询查找与“1”匹配的索引,然后在其中 Name 包含“Fred”(假设 Name 列未编入索引),SQL 会非常快速地获取匹配“1”的行集,然后扫描表以查找其余行。

所以 SQL 真正在做的是试图减少它必须迭代的工作集(行数)。

当您索引一个位字段(或某个狭窄范围)时,您只会通过匹配该值的行数来减少工作集。如果您有少量匹配的行,它将大大减少您的工作集。对于具有 50/50 分布的大量行,与保持索引最新相比,它可能只会给您带来很少的性能提升。

每个人都说要测试的原因是因为 SQL 包含一个非常聪明和复杂的优化器,如果它认为表扫描更快,它可能会忽略索引,或者可能使用排序,或者可能组织内存页面,但它非常喜欢。

【讨论】:

  • 所以听起来如果我只有少数位字段为 1 的行(例如跟踪“IsProcessed”),那么索引会很好,因为它会按值,然后能够非常快速地选择小的工作集。如果您同意,请添加,我会接受。
  • 我在之前的评论中的意思是,如果分布是偏重于一个值。但我喜欢你其余的回答,所以如果你解决了这个问题,我会接受。
  • 完成。我在想,对于一百万行,一个位字段将有 50% 的分布,但你说得对,对于特定的问题空间,它可以大大减少工作集。
  • 看看有无索引的执行计划是值得的,看看是否使用了索引,以及它是否真的降低了查询成本。简单又科学!
  • 索引一个位域+另一个域怎么样?例如。在 Web 活动日志中,可以索引时间戳,但另一个有用的索引可能位于“IsHTTPS”+ 时间戳位字段上,以快速查看所有 https 操作。那也会效率低下吗?
【解决方案2】:

我刚刚通过另一个问题遇到了这个问题。假设您声明只有少数记录假定值为 1(并且那些是您感兴趣的记录),那么过滤索引可能是一个不错的选择。比如:

create index [IX_foobar] on dbo.Foobar (FooID) where yourBitColumn = 1

这将创建一个小得多的索引,优化器足够聪明地使用它作为查询中的谓词。

【讨论】:

  • 值得注意的是,查询中的谓词必须硬编码为过滤索引中的值。如果在参数yourBitColumn = @value 中传递值,则优化器无法确定过滤后的索引是否可用。
  • 有办法解决这个问题,但你是对的;优化器需要在编译时保证与过滤索引谓词匹配的任何谓词的值是静态/不变的,因为优化器的工作是创建适用于任何组参数的通用计划。
【解决方案3】:

1 亿条记录中只有少数的位字段设置为 1?是的,我认为索引位字段肯定会加快查询 bit=1 记录。您应该从索引中获取对数搜索时间,然后只触摸 bit=1 记录的几页。否则,您将不得不触摸 1 亿条记录表的所有页面。

再说一遍,我绝对不是数据库专家,可能会遗漏一些重要的东西。

【讨论】:

    【解决方案4】:

    如果您的分布非常知名且不平衡,例如 99% 的行位 = 1,而 1% 的行位 = 0,那么当您使用位 = 1 执行 WHERE 子句时,将围绕与索引扫描同时进行。如果您想在位 = 0 时进行快速查询,我知道的最好方法是创建一个过滤索引,添加一个 WHERE 位 = 0 子句。这样,该索引将只存储 1% 的行。然后执行 WHERE bit = 0 只会让查询优化器选择该索引,并且其中的所有行都将是 bit = 0。您还可以使用非常少量的磁盘空间来比较位上的完整索引.

    【讨论】:

    • 如果 99% 的行 bit=1,优化器应该忽略索引并进行表扫描。使用索引实际上会比表扫描更糟糕,至少在旋转驱动器上,更多的 I/O 和非连续的磁盘读取。过滤索引(Postgres 等效:部分索引)是要走的路。我猜是因为这个问题已经过了好几年了,所以这个答案没有得到应有的投票。
    【解决方案5】:

    【讨论】:

    • 这篇文章不再可见
    • @Homer6 我添加了一个链接,指向这篇文章的新家。
    • 新链接转到 Toad World 主页。
    • 使用Wayback机器找到文章,又找到了新的相关文章。希望这会有所帮助。
    【解决方案6】:

    虽然我不认为我会单独索引一个位列,但将位列作为复合索引的一部分包含在内是很常见的。

    当您的应用程序几乎总是在寻找活跃的客户时,一个简单的示例是 ACTIVE 上的索引 LASTNAME 而不仅仅是姓氏。

    【讨论】:

    • 在您给出的示例中,我更倾向于将姓氏放在首位。这取决于特定的查询工作负载,但一般来说,优先选择更具选择性的列意味着更有可能使用索引。
    【解决方案7】:

    当然值得,尤其是当您需要按该值检索数据时。这类似于使用稀疏矩阵而不是使用普通矩阵。

    现在使用 SQL 2008,您可以使用分区函数,并且可以过滤索引中的数据。早期版本的缺点是会为所有数据建立索引,但这可以通过将感兴趣的值存储在单独的文件组中来优化。

    【讨论】:

      【解决方案8】:

      正如其他人所说,您需要对此进行衡量。我不记得我在哪里读过这篇文章,但列需要具有非常高的基数(大约 95%)才能使索引有效。您对此的最佳测试是构建索引并检查 BIT 字段的 0 和 1 值的执行计划。如果您在执行计划中看到索引查找操作,那么您就知道您的索引将被使用。

      您最好的做法是使用基本的 SELECT * FROM table WHERE BitField = 1; 来测试查询并从那里逐步构建功能,直到您对应用程序进行实际查询,检查每一步的执行计划以确保仍在使用索引查找。诚然,不能保证这个执行计划会在生产中使用,但很有可能会。

      可以在sql-server-performance.com forums 和引用的article 中找到一些信息

      【讨论】:

      • 整个列的基数并不重要。它是 WHERE 子句的选择性。因此,如果值 1 的列很少,索引仍然可以。如果是 50/50(例如男性/女性),那么就不值得了。
      【解决方案9】:

      “我记得有一次读到,索引低基数(不同值的数量少)的字段并不值得做”

      这是因为 SQL Server 几乎总是会发现只进行表扫描比读取索引更有效。所以基本上你的索引永远不会被使用,维护它是一种浪费。正如其他人所说,在复合索引中可能没问题。

      【讨论】:

        【解决方案10】:

        如果您的目标是更快地查询位字段值等于“1”的记录,您可以尝试基表的索引视图,该视图仅包含位字段等于“1”的记录。在企业版中,如果查询可以使用索引视图而不是指定的表来提高查询性能,它将使用视图。从理论上讲,这将提高仅查找位字段值为“1”的记录的选择查询的速度。

        http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx

        所有这些都假设您是 Microsoft SQL Server 2005 Enterprise。 2008 年可能也是如此,我不熟悉那个版本。

        【讨论】:

          【解决方案11】:

          如果您想知道某个索引是否具有您想要的效果:测试并再次测试。

          一般而言,您不希望索引不足以缩小表的范围,因为维护索引的成本很高。 (成本>利润)。但是,如果您的案例中的索引会将表格切成两半,那么您可能会有所收获,但会将其放在桌子上。这完全取决于您的表格的确切大小/结构以及您使用它的方式(读取/写入次数)。

          【讨论】:

            【解决方案12】:

            就其本身而言,不会,因为它的选择性很小。作为复合索引的一部分。很有可能,但仅在其他相等列之后。

            【讨论】:

              【解决方案13】:

              不能索引 SQL Server 2000 中的位字段,如当时联机丛书中所述:

              整数数据类型 1、0 或 NULL。

              备注

              bit 类型的列不能 有索引。

              是的,如果在数百万行中只有少数行,索引会有所帮助。但是,如果您想在这种情况下执行此操作,则需要将该列设为 tinyint

              注意:企业管理器不允许您在位列上创建索引。如果您希望仍然可以在位列上手动创建索引:

              CREATE INDEX IX_Users_IsActiveUsername ON Users
              (
                 IsActive,
                 Username
              )
              

              但是 SQL Server 2000 实际上不会使用这样的索引 - 运行一个索引是完美候选者的查询,例如:

              SELECT TOP 1 Username 
              FROM Users
              WHERE IsActive = 0
              

              SQL Server 2000 将改为执行表扫描,就好像索引甚至不存在一样。如果将列更改为 tinyint,SQL Server 2000 进行索引查找。此外,以下未涵盖的查询:

              SELECT TOP 1 * 
              FROM Users
              WHERE IsActive = 0
              

              它将执行索引查找,然后是书签查找。


              SQL Server 2005 对位列索引的支持有限。例如:

              SELECT TOP 1 Username 
              FROM Users
              WHERE IsActive = 0
              

              将导致通过覆盖索引进行索引搜索。但未涵盖的情况:

              SELECT TOP 1 * 
              FROM Users
              WHERE IsActive = 0
              

              不会导致索引查找后进行书签查找,它将执行表扫描(或聚集索引扫描),而不是执行索引查找后执行书签查找。

              通过实验和直接观察验证。

              【讨论】:

              • 仅供参考 - SQL Server 2005 Management Studio 确实让您这样做。
              • 我的 SQL Server 2000 副本让我可以在位列上设置索引。
              • 我的 SQL Server 2000 副本不允许我在位列上设置索引。
              【解决方案14】:

              很晚才回答...

              可以,可以是useful according to SQL CAT team(已更新,已合并)

              【讨论】:

              • 该链接现在似乎已失效。但是,该帖子似乎已与其他几个帖子一起合并到 e-book 中。参考部分从第 86 页开始。该电子书可从“SQLCAT 关系引擎指南”链接下的SQLCAT.com eBooks 下载。
              【解决方案15】:

              这是一个常见的查询吗?在寻找“少数”记录时可能是值得的,但在其他行上对您没有太大帮助。还有其他方法可以识别数据吗?

              【讨论】:

                【解决方案16】:

                基数是一个因素,另一个因素是索引对数据的划分程度。如果你有大约一半的 1 和一半的 0,那么它会有所帮助。 (假设该索引是比其他索引更好的选择路径)。但是,您多久插入和更新一次?为 SELECT 性能添加索引也会损害 INSERT、UPDATE 和 DELETE 性能,因此请记住这一点。

                我想说,如果 1 到 0(反之亦然)不优于 75% 到 25%,请不要打扰。

                【讨论】:

                • 我不同意。如果您的分布是 50/50,那么您永远不会使用索引,因为执行表扫描会更快。但是,如果你只有5个,1个值,100万个0值,那么在搜索1的时候很有可能会用到索引。
                【解决方案17】:

                测量前后响应时间,看看是否值得;从理论上讲,它应该可以提高使用索引字段的查询的性能,但它实际上取决于真/假值的分布以及您关心的查询中涉及的其他字段

                【讨论】:

                  【解决方案18】:

                  Ian Boyd 说您无法通过 Enterprise Manager for SQL 2000 执行此操作是正确的(请参阅他关于通过 T-SQL 创建它的说明。

                  【讨论】:

                    【解决方案19】:

                    您需要聪明地在这里查询,如果您的系统中的负载更多,您必须知道列上的负载值,并且您想检查所有的真值写您的查询以检查不是假的..它会有很大帮助,只是骗人的。

                    【讨论】:

                      猜你喜欢
                      • 2021-09-15
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-08-21
                      • 2011-11-28
                      • 2020-07-29
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多