在过去的日子里,这被认为是一个很大的禁忌,select * from table
或者select count(*) from table
因为性能受到影响。
在更高版本的 SQL Server 中是否仍然如此(我使用的是 2012,但我想这个问题适用于 2008 - 2014)?
编辑:由于人们似乎在这里对我略有评价,我从基准/学术的角度来看这个,而不是它是否是“正确”的事情(当然不是)
在过去的日子里,这被认为是一个很大的禁忌,select * from table
或者select count(*) from table
因为性能受到影响。
在更高版本的 SQL Server 中是否仍然如此(我使用的是 2012,但我想这个问题适用于 2008 - 2014)?
编辑:由于人们似乎在这里对我略有评价,我从基准/学术的角度来看这个,而不是它是否是“正确”的事情(当然不是)
如果您
SELECT COUNT(*) FROM TABLE
只返回一行(计数),则相对较轻,并且是获取该数据的方法。并且
SELECT *
不是物理上的禁忌,因为它是合法且允许的。但是,问题
SELECT *
在于您可能会导致更多的数据移动。您对表中的每一列进行操作。如果您SELECT
只包含几列,您可能能够从一个或多个索引中获得答案,从而减少 I/O 以及对服务器缓存的影响。所以,是的,建议不要将其作为一般做法,因为它会浪费您的资源。
唯一真正的好处
SELECT *
是不键入所有列名。但是从 SSMS 中,您可以使用拖放来获取查询中的列名并删除不需要的列名。一个类比:如果有人
SELECT *
在不需要每一列时使用,当他们不需要每一行时,他们是否也会使用SELECT
不带WHERE
(或其他一些限制性子句)?除了答案已经提供者之外,我觉得值得指出的是,开发人员在使用现代 ORM (例如 Entity Framework)时往往过于懒惰。虽然 DBA 尽最大努力避免
SELECT *
,但开发人员经常编写语义等价的代码,例如,在 c# Linq 中:实质上,这将导致以下结果:
还有一个尚未涵盖的额外开销。那就是将每一行中的每一列处理到相关对象所需的资源。此外,对于保存在内存中的每个对象,都必须清理该对象。如果您只选择了您需要的列,您可以轻松节省超过 100mb 的内存。虽然它本身并不是一个巨大的数量,但它是垃圾收集等的累积效应,这是客户端的成本。
所以,是的,至少对我来说,它是并且永远是一个很大的不。我们还需要更多地了解这样做的“隐藏”成本。
附录
这是一个示例,仅根据评论中的要求提取您需要的数据:
性能:带有 SELECT * 的查询可能永远不会是覆盖查询(简单的谈话解释,堆栈溢出解释)。
面向未来:您的查询今天可能返回所有七列,但如果有人在明年添加五列,那么在一年中您的查询将返回十二列,浪费 IO 和 CPU。
索引:如果您希望您的视图和表值函数参与 SQL Server 中的索引,则必须使用模式绑定创建这些视图和函数,这禁止使用 SELECT *。
最佳实践:永远不要
SELECT *
在生产代码中使用。对于子查询,我更喜欢
WHERE EXISTS ( SELECT 1 FROM … )
.编辑:为了解决下面 Craig Young 的评论,在子查询中使用“SELECT 1”不是“优化”——这样我就可以站在我的班级面前说“不要使用 SELECT *,没有例外! "
关于我能想到的唯一例外是客户端正在执行某种数据透视表操作并且确实需要所有当前和未来的列。
我可能会接受涉及 CTE 和派生表的异常,但我想查看执行计划。
请注意,我认为
COUNT(*)
这是一个例外,因为它是“*”的不同语法用法。在 SQL Server 2012(或 2005 年以后的任何版本)中,使用
SELECT *...
只是查询的顶级 SELECT 语句中可能存在的性能问题。所以这在 Views(*)、子查询、EXIST 子句、CTE 或
SELECT COUNT(*)..
等中都不是问题。请注意,这对于 Oracle 和 DB2 可能也是如此,也许是 PostGres(不确定) ,但很可能在很多情况下对于MySql来说仍然是一个问题。要理解为什么(以及为什么它在顶级 SELECT 中仍然可能是一个问题),了解它为什么曾经是一个问题是有帮助的,因为 using
SELECT *..
意味着“返回所有列”。一般来说,这将返回比您真正想要的更多的数据,这显然会导致更多的 IO,包括磁盘和网络。不太明显的是,这也限制了 SQL 优化器可以使用的索引和查询计划,因为它知道它最终必须返回所有数据列。如果它可以提前知道您只需要某些列,那么它通常可以通过利用只有这些列的索引来使用更有效的查询计划。幸运的是,它有一种方法可以提前知道这一点,即您可以在列列表中明确指定所需的列。但是当你使用“*”时,你放弃了这一点,转而支持“给我一切,我会弄清楚我需要什么”。
是的,处理每一列还需要额外的 CPU 和内存使用,但与这两件事相比,它几乎总是很小的:不需要的列所需的大量额外磁盘和网络带宽,以及必须使用更少的优化查询计划,因为它必须包含每一列。
那么发生了什么变化?基本上,SQL 优化器成功地合并了一个称为“列优化”的功能,这意味着,如果您要在查询的上层中实际使用列,它们现在可以在下层子查询中计算出来。
这样做的结果是,如果您在查询的较低/内部级别使用 'SELECT *..' 就不再重要了。相反,真正重要的是顶级 SELECT 的列列表中的内容。除非您
SELECT *..
在顶部使用,否则它必须再次假定您想要所有列,因此无法有效地使用列优化。(* -- 请注意,视图中存在一个不同的、较小的绑定问题,
*
当使用“*”时,它们并不总是在列列表中注册更改。还有其他方法可以解决这个问题,并且不会影响性能。)还有一个不使用的小理由
SELECT *
:如果返回的列的顺序发生变化,您的应用程序将中断……如果您幸运的话。如果你不是,你会有一个微妙的错误,可能很长一段时间都没有被发现。表中字段的顺序是应用程序永远不应该考虑的实现细节,因为它唯一可见的时候是如果你使用SELECT *
.它在物理上和问题上都允许使用
select * from table
,但是,这是一个坏主意。为什么?首先,您会发现您正在返回不需要的列(资源繁重)。
其次,在大表上命名列将花费更长的时间,因为当您选择 * 时,您实际上是从数据库中选择列名并说“给我与在另一个列表中具有名称的列相关联的数据。” 虽然这对程序员来说很快,但想象一下在银行的计算机上进行这种查找,一分钟内可能会进行数十万次查找。
第三,这样做实际上使开发人员更难。您需要多久从 SSMS 到 VS 来回切换一次才能获得所有列名?
第四,这是懒惰编程的标志,我认为任何开发人员都不会想要这种声誉。
如果将
Select * ...
代码放入程序中可能会出现问题,因为如前所述,数据库可能会随着时间的推移而发生变化,并且列数可能比您编写查询时预期的要多。这可能会导致程序失败(最好的情况),或者程序可能会继续运行并破坏一些数据,因为它正在查看它不是为了处理而编写的字段值。简而言之,生产代码应始终指定要在SELECT
.Select *
话虽如此,当is 子句的一部分时,我的问题较少EXISTS
,因为将返回给程序的所有内容都是一个布尔值,指示选择成功或失败。其他人可能不同意这一立场,我尊重他们对此的看法。编码的效率可能Select *
比在子句中编码“选择 1”的效率略低EXISTS
,但我不认为有任何数据损坏的危险,无论哪种方式。很多答案为什么
select *
是错的,所以当我觉得它是正确的或至少是好的时,我会覆盖。1)在EXISTS中,查询的SELECT部分的内容被忽略了,所以你甚至可以写
SELECT 1/0
,它不会出错。EXISTS
只是验证一些数据会返回并基于此返回一个布尔值。2)这可能会引发一场风暴,但我喜欢
select *
在我的历史表触发器中使用。通过select *
,它可以防止主表在不将该列添加到历史表的情况下获取新列,以及在插入/更新/删除到主表时立即出错。这防止了开发人员多次添加列而忘记将其添加到历史表中。