在SQL Server 2012上,select *是否仍然是一大禁忌?


41

在过去的日子里,它被认为是一个很大的禁忌,select * from table或者select count(*) from table因为性能受到影响。

在更高版本的SQL Server中是否仍然如此(我使用的是2012,但我想这个问题将适用于2008-2014)?

编辑:由于人们似乎在这里对我有些偏颇,所以我正在从基准/流行病的角度看待这个问题,而不是是否要做“正确”的事情(当然不是)

Answers:


50

如果SELECT COUNT(*) FROM TABLE只返回一行(计数),则相对较轻,并且是获取该基准的方法。

SELECT *不是物理上的禁忌,因为这是合法且允许的。

但是,问题SELECT *在于您可能导致更多的数据移动。您对表中的每一列进行操作。如果SELECT仅包含几列,则可以从一个或多个索引中获得答案,这可以减少I / O以及对服务器缓存的影响。

因此,是的,建议您不要这样做,因为这会浪费您的资源。

唯一真正的好处SELECT *是不必键入所有列名。但是从SSMS中,您可以使用拖放操作来获取查询中的列名,并删除不需要的列名。

打个比方:如果某人使用SELECT *时不需要每一列,那么当他们不需要每一行时是否也会使用SELECT而没有WHERE(或其他限制子句)?


24

除了已经提供的答案之外,我觉得值得指出的是,开发人员在使用诸如实体框架之类的现代ORM时往往过于懒惰。尽管DBA尽力避免SELECT *,但是开发人员经常在c#Linq中编写语义等效的代码:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User").ToList();

本质上,这将导致以下结果:

SELECT * FROM MyTable WHERE FirstName = 'User'

还存在尚未解决的额外开销。那就是将每一行中的每一列处理到相关对象所需的资源。此外,对于保存在内存中的每个对象,必须清除该对象。如果仅选择所需的列,则可以轻松节省超过100mb的内存。虽然它本身不是一个很大的数量,但是它的垃圾回收等累积作用是成本方面的。

所以是的,至少对我来说,这将永远是一个大问题。我们还需要教育这样做的“隐性”成本。

附录

以下是根据注释要求仅提取所需数据的示例:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User")
                             .Select(entity => new { entity.FirstName, entity.LastNight });

13

性能:带有SELECT *的查询可能永远不会是覆盖查询(简单说明堆栈溢出说明)。

面向未来:您的查询今天可能返回所有七列,但是如果有人在明年添加五列,则一年后您的查询将返回十二列,这浪费了IO和CPU。

索引:如果希望视图和表值函数参与SQL Server中的索引,则必须使用架构绑定来创建这些视图和函数,这禁止使用SELECT *。

最佳实践:切勿SELECT *在生产代码中使用。

对于子查询,我更喜欢WHERE EXISTS ( SELECT 1 FROM … )

编辑:要解决Craig Young在下面的评论,在子查询中使用“ SELECT 1”不是“优化”-是的,因此我可以站在我的课前说“不要使用SELECT *,无例外!” ”

关于我可以想到的唯一例外是,客户端正在执行某种数据透视表操作,并且确实需要所有现在和将来的列。

尽管我想查看执行计划,但我可能会接受涉及CTE和派生表的异常。

请注意,我认为COUNT(*)这是一个例外,因为它是“ *”的不同语法用法。


10

在SQL Server 2012(或2005年以后的任何版本)中,使用SELECT *...仅是查询的顶级SELECT语句中可能的性能问题。

因此,在Views(*),子查询,EXIST子句,CTE或其他SELECT COUNT(*)..等等中,这都不是问题。请注意,这对于Oracle,DB2和PostGres 也可能适用(不确定) ,但是很可能在许多情况下,对于MySql来说,这仍然是一个问题。

要了解原因(以及为什么它仍然是顶级SELECT中的问题),有助于理解为什么它曾经是一个问题是有帮助的,这是因为使用SELECT *..意味着“ 返回所有列 ”。一般来说,这将返回大量的数据比你真正想要的,这显然会导致其它更多的IO,磁盘和网络。

不太明显的是,这也限制了SQL优化器可以使用的索引和查询计划,因为它知道最终必须返回所有数据列。如果它可以提前知道只需要某些列,那么它通常可以利用只有那些列的索引来使用更有效的查询计划。幸运的是,有一种方法可以让它提前知道这一点,即可以在列列表中显式指定所需的列。但是,当您使用“ *”时,您就放弃了,而赞成“只要给我所有东西,我就会弄清楚我需要什么”。

是的,处理每一列还需要额外的CPU和内存使用量,但是与这两件事相比,它几乎总是很小的:不需要的列需要大量的额外磁盘和网络带宽,并且必须使用更少的资源优化的查询计划,因为它必须包含每列。

那么,什么改变了?基本上,SQL Optimizers成功地合并了一个称为“列优化”的功能,该功能仅表示,如果您要在查询的较高级别中实际使用列,则它们现在可以在较低级别的子查询中找到。

这样做的结果是,如果在查询的较低/较高级别中使用“ SELECT * ..”,则不再重要。相反,真正重要的是顶级SELECT的列列表中的内容。除非您SELECT *..在顶部使用,否则必须再次假定您需要所有列,因此不能有效地使用列优化。

(*-注意,在View中存在一个不同的较小的绑定问题,*即使用“ *”时它们并不总是在列列表中注册更改。还有其他方法可以解决此问题,并且这不会影响性能。)


5

还有一个不使用的小原因SELECT *:如果返回的列顺序发生更改,则您的应用程序将中断...如果您幸运的话。如果不是,您将遇到一个细微的错误,该错误可能很长一段时间都不会被发现。表格中字段的顺序是实现的详细信息,应用程序绝对不应考虑,因为使用时,唯一可见的情况就是如此SELECT *


4
这无关紧要。如果您要在应用程序代码中按列索引访问列,那么您应该拥有一个损坏的应用程序。按名称访问列始终会产生更具可读性的应用程序代码,并且几乎永远不会成为性能瓶颈。
Lie Ryan

3

物理上和问题上都允许使用select * from table它,但是,这是一个坏主意。为什么?

首先,您会发现您正在返回不需要的列(资源繁重)。

其次,与命名列相比,在大表上花费的时间更长,因为当您选择*时,实际上是从数据库中选择列名称,然后说“给我与其他列表中具有名称的列关联的数据。” 尽管这对于程序员来说是快速的,但请想象一下,在银行的计算机上进行查找,该计算机在一分钟内可能实际上有成千上万次查找。

第三,这样做实际上会使开发人员更难。您需要多久从SSMS来回切换到VS才能获得所有列名?

第四,这是懒惰编程的标志,我认为没有任何开发人员会想要这种声誉。


当前形式的第二个参数有一些小错误。首先,所有RDBMS都会缓存表的方案,主要是因为无论如何该方案都将在查询解析阶段加载,以确定查询中表中存在或缺少的列。因此,查询解析器已经自行查询了列名称列表,并立即将*替换为列列表。然后,大多数RDBMS引擎都将尝试缓存所有可能的内容,因此,如果发出SELECT * FROM表,则将缓存已编译的查询,因此不会每次都进行解析。而且开发人员很懒:-)
Gabor Garami 2014年

关于你的第二个参数,这是一个普遍的误解-与SELECT *问题没有元数据查找,因为如果你的名字列,SQL Server仍然存在,以验证他们的姓名,检查数据类型,等等
阿龙贝特朗

@Gabor将SELECT *放在视图中时,会发生SELECT *的问题。如果更改了基础架构,则视图可能会变得混乱-它现在具有表架构(自己的)与表本身不同的概念。我在这里谈论这个
亚伦·伯特兰

3

如果将Select * ...代码放在程序中,可能会出现问题,因为如前所述,数据库可能会随时间变化,并且列的数量比编写查询时的预期要多。这可能会导致程序失败(最佳情况),或者程序可能会继续前进并破坏某些数据,因为它正在查看未编写为处理的字段值。简而言之,生产代码应始终在中指定要返回的字段SELECT

话虽如此,当Select *成为EXISTS子句的一部分时,我的问题就更少了,因为要返回给程序的所有内容都是一个布尔值,指示选择的成功与否。其他人可能不同意这种立场,我尊重他们对此的看法。编码的效率可能Select *比在EXISTS子句中编码“选择1”的效率低一些,但我认为,无论哪种方式,都不会存在数据损坏的危险。


实际上,是的,我原本打算引用EXISTS子句。我的错。
马克·罗斯

2

很多答案为什么select *错了,所以我会在我认为正确或至少可以的时候进行介绍。

1)在EXISTS中,查询的SELECT部分​​的内容将被忽略,因此您甚至可以编写SELECT 1/0,并且不会出错。EXISTS只是验证某些数据将返回,并根据此返回布尔值。

IF EXISTS(
    SELECT * FROM Table WHERE X=@Y
)

2)这可能会select *引发火灾,但是我喜欢在历史记录表触发器中使用。通过select *,它防止主表获取新列而不将其添加到历史表中,并且在将主表插入/更新/删除时会立即出错。这避免了开发人员多次添加列而忘记将其添加到历史表的情况。


3
我仍然更喜欢,SELECT 1因为它最明显地将您的意图通知未来的代码维护者。这不是必需的,但是如果我看到... WHERE EXISTS (SELECT 1 ...)它很明显就宣布自己是一个真相测试。
swasheck

1
@zlatan许多人都SELECT 1基于一个神话,那就是性能会好于SELECT *。但是,两种选择都是完全可以接受的。由于优化器处理EXISTS的方式,性能没有差异。在可读性上也没有任何区别,因为“ EXISTS”一词清楚地表明了事实测试。
2014年

关于第二点,我理解您的推理,但是仍然存在风险。让我“为您绘制一个方案” ...开发人员Column8在主表中添加了忘记历史记录表的内容。开发人员将一堆代码写到列8中。然后,他将添加Column9到主表中。这次记得也增加了历史。后来在测试时,他意识到他忘记添加Column9历史记录(由于您的错误检测技术所致),因此立即添加了它。现在触发器似乎起作用了,但是历史记录中混入了第8列和第9列中的数据。:S
幻灭了

继续...重点是,上述“混合”方案只是可能导致错误检测技巧使您失败并实际上使情况变得更糟的众多方案之一。基本上,您需要更好的技术。一种不依赖触发器的假设,它假设您从中选择的表中列的顺序。建议:-对您的常见错误的清单进行个人代码审查。-同行代码审查。-跟踪历史的替代技术(我个人认为基于触发器的机制是被动的而不是主动的,因此容易出错)。
幻灭了

@CraigYoung这是一种可能性。但是,如果他们这样做,我会节制。这不是您容易犯的错误
UnhandledExcepSean 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.