在过去的日子里,由于性能下降,被认为是一个很大的禁忌。由于性能下降。

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

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

#1 楼

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

SELECT *并非物理禁忌,因为这是合法的,

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

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

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

类推:如果某人在不需要每个列时都使用SELECT *,则会当它们不需要每一行时,它们也使用不带SELECT(或其他限制条款)的WHERE吗?

#2 楼

除了已经提供的答案之外,我觉得值得指出的是,开发人员在使用诸如实体框架之类的现代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 });


#3 楼

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

面向未来:您的查询今天可能返回所有七列,但如果有人添加五列在接下来的一年中,一年之后,您的查询将返回十二列,从而浪费IO和CPU。

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

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

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

编辑:要解决以下Craig Young的评论,在子查询中使用“ SELECT 1”不是“优化”,而是让我站在班级前面说“不要”不要使用SELECT *,没有例外!“

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

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

请注意,我将COUNT(*)视为此异常,因为它是一种不同的语法使用“ *”。

#4 楼

在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 *..,否则必须再次假设您要使用所有列,因此不能有效地使用列优化。

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

#5 楼

还有一个不使用SELECT *的小原因:如果返回的列顺序发生更改,则您的应用程序将中断...如果您幸运的话。如果不是,您将遇到一个细微的错误,该错误可能很长一段时间都不会被发现。表中字段的顺序是实现细节,应用程序永远不应考虑它,因为只有当您使用SELECT *时,它才是可见的。

评论


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

– Lie Ryan
2014年7月2日在17:58



#6 楼

在物理上和在问题上都可以使用select * from table,但是,这不是一个好主意。为什么?

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

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

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

第四,这是懒惰编程的标志,我认为任何开发人员都不会想要那个声誉。

评论


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

–Gabor Garami
2014年7月3日,11:18



关于第二个参数,这是一个常见的误解-SELECT *的问题不是元数据查找,因为如果您命名列,SQL Server仍然必须验证其名称,检查数据类型等。

–亚伦·伯特兰(Aaron Bertrand)
2014年7月3日在21:39

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

–亚伦·伯特兰(Aaron Bertrand)
2014年7月3日在21:40

#7 楼

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

话虽如此,当Select *属于EXISTS子句的一部分时,我的问题较少,因为所有内容都将被返回程序的布尔值表示选择的成功或失败。其他人可能不同意这种立场,我尊重他们对此的看法。编码Select *的效率可能比在EXISTS子句中编码“选择1”的效率稍低,但是我认为这两种方式都不会存在数据损坏的危险。

评论


确实,是的,我打算引用EXISTS子句。我的错。

–马克·罗斯(Mark Ross)
2014年7月5日在6:11

#8 楼

为什么select *是错误的,所以有很多答案,因此当我感觉正确或至少可以确定时,我将进行介绍。

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

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


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

评论


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

–swasheck
2014年7月3日在4:07

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

–幻灭
2014年7月9日在15:05

关于第二点,我理解您的推理,但是仍然存在风险。让我“为您绘制一个方案” ...开发人员将Column8添加到主表中,而忘记了历史记录表。开发人员将一堆代码写到列8中。然后,他将Column9添加到主表中。这次记得也增加了历史。后来在测试时,他意识到他忘记将Column9添加到历史记录中(这要感谢您的错误检测技术),并迅速添加了它。现在,触发器似乎可以正常工作,但是历史记录中混合了第8和9列中的数据。 :S

–幻灭
2014年7月9日15:14

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

–幻灭
2014年7月9日15:21

@CraigYoung这是可能的。但是,如果他们这样做,我会节制。这不是您容易犯的错误

–UnhandledExcepSean
2014年7月9日15:21