116

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

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

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

4

19 回答 19

80

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

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

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

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

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

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

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

于 2008-10-23T20:41:35.537 回答
22

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

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

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

于 2013-09-23T21:26:52.880 回答
11

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

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

于 2008-10-23T20:58:56.267 回答
8

如果你还没有读过它,Jason Massie 最近写了一篇文章来讨论这个话题。

http://statisticsio.com/Home/tabid/36/articleType/ArticleView/articleId/302/Never-Index-a-BIT.aspx

编辑:新文章位置 - http://sqlserverpedia.com/blog/sql-server-bloggers/never-index-a-bit

以前“新”文章位置的回退机:http://web.archive.org/web/20120201122503/http: //sqlserverpedia.com/blog/sql-server-bloggers/never-index-a-bit/

新的 SQL Server Pedia 位置是 Toadworld,其中有一篇来自 Kenneth Fisher 的新文章讨论了这个主题:

http://www.toadworld.com/platforms/sql-server/b/weblog/archive/2014/02/17/dba-myths-an-index-on-a-bit-column-will-never-be-使用过的.aspx

回程机: http ://web.archive.org/web/20150508115802/http://www.toadworld.com/platforms/sql-server/b/weblog/archive/2014/02/17/dba-myths-an -index-on-a-bit-column-will-never-be-used.aspx

于 2009-01-09T17:11:15.013 回答
8

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

于 2015-05-21T19:15:07.070 回答
7

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

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

于 2008-10-23T19:54:36.333 回答
2

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

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

于 2008-10-23T19:39:12.793 回答
2

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

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

于 2008-10-23T19:42:35.657 回答
2

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

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

一些信息可以在sql-server-performance.com 论坛和参考文章中找到

于 2008-10-23T19:47:18.797 回答
2

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

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

于 2008-10-23T20:07:12.533 回答
2

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

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

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

于 2008-10-23T21:41:03.467 回答
1

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

少量

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

评论

bit类型的列上不能有索引。

是的,如果你只有几行,数百万,索引会有所帮助。但是,如果您想在这种情况下执行此操作,则需要将列设为 a 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

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

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

于 2008-10-23T20:29:04.393 回答
1

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

于 2008-10-23T20:31:05.467 回答
1

很晚的答案...

是的,根据 SQL CAT 团队的说法,它可能很有用(已更新,已合并)

于 2011-12-21T15:20:26.117 回答
0

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

于 2008-10-23T19:38:32.653 回答
0

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

于 2008-10-23T19:38:43.683 回答
0

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

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

于 2008-10-23T20:13:22.070 回答
0

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

于 2010-02-09T23:49:09.060 回答
0

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

于 2017-04-01T12:54:33.797 回答