为啥我不能简单地添加一个包含所有列的索引?

Posted

技术标签:

【中文标题】为啥我不能简单地添加一个包含所有列的索引?【英文标题】:Why can't I simply add an index that includes all columns?为什么我不能简单地添加一个包含所有列的索引? 【发布时间】:2011-07-23 19:25:42 【问题描述】:

我在 SQL Server 数据库中有一个表,我希望能够尽可能快地搜索和检索数据。我不关心插入表格需要多长时间,我只关心我可以获取数据的速度。

问题是使用 20 种或更多不同类型的查询访问该表。这使得添加专门为每个查询设计的索引成为一项繁琐的任务。我正在考虑简单地添加一个包含表的所有列的索引。这不是你在“好”数据库设计中通常会做的事情,所以我假设我不应该这样做是有充分理由的。

谁能告诉我为什么我不应该这样做?

更新:我忘了提,我也不关心我的数据库的大小。没关系,这意味着我的数据库大小将增长到超过所需的大小

【问题讨论】:

“为什么我不能简单地添加一个包含所有列的索引?” 那不就是……表格吗? @T.J.Crowder 是,但不完全是。 @T.J.Crowder -- 列的顺序非常重要。一个简单的示例是在一个多:多映射表中,您需要在其中双向进行。参见示例here 【参考方案1】:

首先,SQL Server 中的索引在其索引条目中最多只能有 900 个字节。仅此一项就不可能为所有列建立索引。

最重要的是:这样的索引根本没有意义。你想达到什么目的??

考虑一下:如果您在 (LastName, FirstName, Street, City) 上有一个索引,那么该索引将能够用于加快对

的查询 FirstName一个人 City Street

该索引将有助于搜索

(LastName),或 (LastName, FirstName),或 (LastName, FirstName, Street),或 (LastName, FirstName, Street, City)

但实际上没有别的——如果您只搜索 Street 或只搜索 City,肯定不会!

索引中列的顺序有很大的不同,查询优化器不能只使用索引中间某处的任何列进行查找。

考虑一下您的电话簿:它可能按姓氏、名字或街道排序。那么该索引是否可以帮助您找到您所在城市的所有“Joe's”?所有住在“大街”的人??不-您可以先按姓氏查找-然后您可以在该组数据中获得更具体的信息。仅仅对所有内容都有一个索引并不能帮助加快搜索所有列的速度根本

如果您希望能够通过 Street 进行搜索 - 您需要在 (Street) 上添加一个单独的索引(可能还有一两列有意义)。

如果您希望能够通过 Occupation 或其他方式进行搜索 - 您需要另一个特定的索引。

仅仅因为您的列存在于索引中并不意味着会加快对该列的所有搜索!

主要规则是:使用尽可能少的索引 - 对于系统而言,过多的索引可能比完全没有索引更糟糕......构建您的系统,监控其性能,并找到那些花费大多数 - 然后优化这些,例如通过添加索引。

不要仅仅因为你可以就盲目地索引每一列 - 这是对糟糕的系统性能的保证 - 任何索引也需要维护和保养,所以你拥有的索引越多,你的 INSERT、UPDATE 和 DELETE 操作就越多受苦(变慢),因为所有这些索引都需要更新。

【讨论】:

很好的答案,谢谢。您提到了索引的顺序:提到的索引是否适用于“WHERE LastName = 'a' ORDER BY FirstName”和“WHERE FirstName = 'a' ORDER BY LastName”? @Niels Brinch:索引始终适用于它包含的前 n 列;如果它在 (LastName, FirstName) 上,它将有助于 WHERE 或 ORDER BY on (LastName) 或 (LastName,FirstName) - 但不是单独用于 (FirstName) (既不是 WHERE FirstName = ... 也不是ORDER BY FirstName 将受益于这样的索引)。 这是否意味着“WHERE FirstName = 'a' ORDER BY LastName”不会受益于“LastName, FirstName”索引? @Niels Brinch:在这种情况下,ORDER BY LastName 将受益 - WHERE FirstName = 'a'不会受益 非常感谢马克。我现在将研究使用查询的不同部分的顺序。我不知道这个命令有这么大的影响。【参考方案2】:

您对索引的工作方式存在根本性的误解。

阅读此说明“how multi-column indexes work”。

您可能会遇到的下一个问题是,为什么不创建 one index per column--但如果您尝试达到最佳选择性能,这也是一个死胡同。

您可能会觉得这是一项乏味的任务,但我会说这是一项必需仔细索引的任务。马虎的索引反击,如this example。

注意:我坚信正确的索引是有回报的,而且我知道很多人都有与您相同的问题。这就是为什么我要写一本关于它的免费书。上面的链接引用了可能帮助您回答问题的页面。但是,您可能还想从beginning 阅读它。

【讨论】:

谢谢。不,那不是我的下一个问题;)【参考方案3】:

...如果您添加一个包含所有列的索引,并且查询实际上能够使用该索引,它将按主键的顺序扫描它。这意味着几乎要创下所有记录。平均搜索时间为 O(n/2).. 与访问实际数据库相同。

你需要阅读很多关于索引的bit

如果您认为表上的索引有点像 C# 中的字典,这可能会有所帮助。

var nameIndex = new Dictionary<String, List<int>>();

这意味着 name 列被索引,并将返回一个主键列表。

var nameOccupationIndex = new Dictionary<String, List<Dictionary<String, List<int>>>>();

这意味着名称列+职业列被索引。现在想象一下索引包含 10 个不同的列,嵌套的深度到目前为止它包含表中的每一行。

请注意,这并不是它的工作原理。但它应该让您了解如果在 C# 中实现索引是如何工作的。您需要做的是基于广泛查询的一两个键创建索引,这样索引比扫描整个表更有用。

【讨论】:

这肯定取决于 DBMS 是否对单独索引中的列进行索引? 我永远不会尝试分析在查询过程中会遇到多少数据,因为 OP 没有向我们提供数据的基数,没有描述它们的性质,也没有向我们展示他对数据执行的查询. 索引也往往是有序的数据结构,对于列中的无序数据则不能这样说。 O(n) 是无序数据上最快的搜索。 是的,对于 B-Tree 索引,查找速度是 O(logn),这绝对优于 O(n/2) 对不起,你是对的。在非唯一的无序字段上,搜索是 O(n)。在唯一的无序字段上,搜索将是 O(n/2) 平均情况。那是在表扫描上,如果有一个包含每一列的索引,这基本上会发生。它永远不会被使用。【参考方案4】:

如果这是一个数据仓库类型的操作,其中查询针对 READ 查询进行了高度优化,并且如果您有 20 种剖析数据的方法,例如

WHERE 子句涉及..

 Q1: status, type, customer
 Q2: price, customer, band
 Q3: sale_month, band, type, status
 Q4: customer
 etc

而且您绝对有足够的快速存储空间可供刻录,然后无论如何每列单独创建一个索引。因此,一个 20 列的表将有 20 个索引,每个单独的列都有一个。我可能会说忽略位列或低基数列,但是既然我们已经走了这么远,为什么还要麻烦(带着那个警告)。他们只会坐在那里搅动 WRITE 时间,但如果你不关心图片的那一部分,那么我们都很好。

分析您的 20 个查询,如果您的热门查询(最热门的查询)仍然不会更快,请使用 SSMS(按 Ctrl-L)在查询窗口中使用一个查询进行计划。它会告诉你什么索引可以帮助查询——只需创建它;全部创建它们,完全记住这会再次增加写入成本、备份文件大小、数据库维护时间等。

【讨论】:

为每列创建索引只会使每个查询中的第一个参数受益。这就是为什么我对创建一个包含所有列而不是每列一个索引的索引感到好奇的原因。【参考方案5】:

我正在考虑简单地添加一个包含表所有列的索引。

这总是一个坏主意。数据库中的索引不是某种神奇的小精灵。您必须分析您的查询,并根据查询的内容和方式 - 附加索引。

这不是“把所有东西都添加到索引然后打个盹”那么简单

【讨论】:

谢谢,我很怀疑,这就是为什么我的问题是为什么这是一个坏主意。 这是一个“坏主意”,只是因为它很可能是一种浪费。如果列的顺序与表中的相同,则很可能是浪费。对于可以利用第一列进行过滤和/或排序的查询,可能不会浪费。【参考方案6】:

1) 大小,索引本质上是在该列中构建数据的副本,一些易于搜索的结构,如二叉树(我不知道 SQL Server 规范)。 2)您提到了速度,索引结构的添加速度较慢。

【讨论】:

我评论了我自己的问题,说插入速度等在我的情况下并不重要。我很抱歉没有把它放在原来的问题中。【参考方案7】:

我认为提问者在问

'为什么我不能做一个类似'的索引'

create index index_name
on table_name
(
    *
)

问题已经解决了。

但听起来他们正在使用 MS sql 服务器。 了解您可以在索引中包含非键列是很有用的,因此这些列的值可用于从索引中检索,但不能用作选择标准:

create index index_name
on table_name
(
    foreign_key
)
include (a,b,c,d) -- every column except foreign key

我创建了两个包含一百万行相同的表

我这样索引表 A


create nonclustered index index_name_A
on A
(
    foreign_key -- this is a guid
)

还有像这样的表B

create nonclustered index index_name_B
on B
(
    foreign_key -- this is a guid
)
include (id,a,b,c,d) -- ( every key except foreign key)

毫不奇怪,表 A 的插入速度稍快。

但是当我运行这些查询时

select * from A where foreign_key = @guid
select * from B where foreign_key = @guid

在表A上,sql server甚至没有使用索引,它做了一个表扫描,并抱怨缺少索引,包括id、a、b、c、d

在表 B 上,查询速度提高了 50 倍以上,而 io 却少得多

强制 A 上的查询使用索引并没有使其更快

select * from A where foreign_key = @guid
select * from A with (index(index_name_A)) where foreign_key = @guid

【讨论】:

【参考方案8】:

该索引将与您的表相同(可能按其他顺序排序)。 它不会加快您的查询速度。

【讨论】:

好像你说如果索引中的数据是排序的 - 为什么它不加快选择查询? ;-) 因为它需要与表扫描一样多的 I/O,并且不会按不同的 joinwhere 所需的列进行排序。 我们不知道他执行了哪些查询。即使对于连接查询或任何可能永远不会访问表数据的查询(因为所有需要的数据都在索引中)。 更不用说 DBMS 缓存查询和其他东西了。 我相信这里的正确答案是,它会加快我对一个非常具体的查询的查询速度,而对于其他查询,索引将被忽略或不可用。

以上是关于为啥我不能简单地添加一个包含所有列的索引?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL:为啥我的索引长度是 0.0 字节?

为啥我不能使用引用 SQL 中其他列的 checkConstraint 将列添加到现有表

包含列的索引:SQL Server索引的阶梯级别5

具有包含性列的索引

创建带包含列的索引 sqlserver

PostgreSQL 上所有列的索引