为什么SELECT *被认为有害?


256

为什么是SELECT *坏习惯?如果添加了想要的新列,这是否意味着更少的代码更改?

我知道这SELECT COUNT(*)在某些数据库上是性能问题,但是如果您真的想要每一列怎么办?


30
SELECT COUNT(*)变坏的年代令人难以置信的过时。有关信息SELECT *-请参见:stackoverflow.com/questions/1960036/...
OMG小马

8
SELECT COUNT(*)给出的答案与SELECT COUNT(SomeColumn)除非该列为NOT NULL列的答案不同。优化器可以给予SELECT COUNT(*)特殊待遇-通常是这样。另请注意,WHERE EXISTS(SELECT * FROM SomeTable WHERE ...)已给予特殊情况处理。
乔纳森·莱夫勒

3
@Michael Mrozek,实际上是问题的反面。我问它是否有害,而不是有害。
Theodore R. Smith

1
@Bytecode Ninja:具体来说,带有MyISAM引擎的MySQL对COUNT(*)进行了优化:mysqlperformanceblog.com/2007/04/10/count-vs-countcol
Piskvor在

Answers:


312

确实有三个主要原因:

  • 将数据转移到消费者方面效率低下。 当您选择SELECT *时,您经常从数据库中检索到比应用程序真正需要的更多列。这将导致更多数据从数据库服务器移动到客户端,从而减慢访问速度并增加计算机上的负载,并花费更多时间在网络上传输。当有人向原始使用者编码其数据访问权时不存在且不需要的基础表中添加新列时,尤其如此。

  • 索引问题。 考虑一种情况,在这种情况下您希望将查询调整到较高的性能水平。如果要使用*,并且它返回的列数超出实际需要,则服务器通常不得不执行比其他方式更昂贵的方法来检索数据。例如,您将无法创建仅覆盖SELECT列表中各列的索引,即使您这样做了(包括所有列[ shudder ]),下一个出现并在基础层中添加一列的家伙表会导致优化器忽略优化的覆盖率索引,并且您可能会发现查询的性能会由于没有显而易见的原因而大幅下降。

  • 绑定问题。当您选择*时,可以从两个不同的表中检索两个具有相同名称的列。这通常会使您的数据使用者崩溃。想象一下一个将两个表联接在一起的查询,两个表都包含一个名为“ ID”的列。消费者如何知道是哪个?当基础表结构发生更改时,SELECT *还可能使视图(至少在某些版本的SQL Server中)混乱(不重建视图),并且返回的数据可能毫无意义。最糟糕的是,您可以根据自己的喜好命名列,但是下一个出现的家伙可能无法知道他是否会担心添加会与您已经开发的列发生冲突的列名称。

但这对SELECT *并非全无好处。我将它们自由地用于以下用例:

  • 临时查询。 当尝试调试某些东西时,尤其是在我可能不熟悉的狭窄表上,SELECT *通常是我最好的朋友。它可以帮助我了解正在发生的事情,而不必进行大量有关基础列名的研究。列名称越长,它的“加号”就越大。

  • *表示“一行”。 在以下用例中,SELECT *很好,并且有传言称它是性能杀手,只是城市传说,它可能已经在很多年前生效,但现在不行了:

    SELECT COUNT(*) FROM table;

    在这种情况下,*表示“计算行数”。如果使用列名代替*,它将计算该列的值不为null的行。对我来说,COUNT(*)真正使您了解了对行进行计数的概念,并且避免了由于从集合中消除了NULL而导致的奇怪边缘情况。

    这种类型的查询也是如此:

    SELECT a.ID FROM TableA a
    WHERE EXISTS (
        SELECT *
        FROM TableB b
        WHERE b.ID = a.B_ID);

    在任何值得盐分的数据库中,*仅表示“一行”。放在子查询中的内容都没有关系。有些人在SELECT列表中使用b的ID,否则他们将使用数字1,但是IMO这些约定几乎是荒谬的。您的意思是“计数行”,这就是*的含义。大多数查询优化器都足够聪明来知道这一点。(尽管说实话,我只知道这对于SQL Server和Oracle是正确的。)


17
使用联接时,使用“ SELECT id,name”与“ SELECT *”一样可能从两个不同的表中选择两个具有相同名称的列。在两种情况下,使用表名前缀都可以解决该问题。
米哈尔Tatarynowicz

1
我知道这个年龄较大,但这是在谷歌搜索时被拉起的,所以我问。“当*表示“连续”时。在以下用例中,SELECT *很好,而谣言说它是性能杀手,只是城市传说而已...”在这里您是否有任何引用?这是因为硬件功能更强大(如果是这种情况,这并不意味着它效率不低,只是您不太可能注意到它)。我并不是要自己进行第二次猜测,我只是想知道该声明的来源。
杰瑞德(Jared)2012年

6
就引用而言,您可以检查查询计划-在子查询中带有“ *”的情况与选择列时的情况是相同的。它们是相同的,因为基于成本的优化器在语义上“识别”,您所谈论的是满足条件的任何行-这不是硬件或速度问题。
戴夫·马克尔2012年

4
使用的另一个优势*是,在某些情况下,它可以更好地利用MySQL的缓存系统。如果您运行大量select要求使用不同列名(select A where Xselect B where X...)的类似查询,select * where X则将使缓存处理大量查询,这可能会大大提高性能。这是一个特定于应用程序的场景,但是请记住这一点。
本D

2
8年后,但想补充一点关于歧义的观点,但并未提及。处理数据库中的200多个表并混合使用命名约定。在查看与查询结果交互的代码时,SELECT *强制开发人员查看所涉及的表模式,以确定受影响的/可用的列,例如foreachserialize。反复查看架构以跟踪发生的事情的任务将不可避免地增加调试和开发相关代码的总时间。
fyrye '19

91

SELECT语句中的星号字符“ *”是查询所涉及的表中所有列的简写形式。

性能

*速记速度可能很慢,因为:

  • 并非所有字段都被索引,从而强制进行全表扫描-效率较低
  • SELECT *通过电汇保存的内容可能会受到全表扫描的风险
  • 返回的数据超出了所需
  • 使用可变长度数据类型返回尾随列可能会导致搜索开销

保养

使用时SELECT *

  • 不熟悉代码库的人将被迫查阅文档,以了解在能够进行有效更改之前要返回哪些列。从长远来看,使代码更具可读性,最大程度地减少了不熟悉代码的人所需要的歧义和工作,从而节省了更多的时间和精力。
  • 如果代码取决于列顺序,SELECT *则如果表的列顺序已更改,则将隐藏等待发生的错误。
  • 即使您在编写查询时需要每一列,将来也可能不是这种情况
  • 使用使配置复杂化

设计

SELECT *反模式

  • 查询的目的不太明显。应用程序使用的列是不透明的
  • 它打破了关于尽可能使用严​​格类型的模块化规则。显式通用性更好。

什么时候应该使用“ SELECT *”?

SELECT *当涉及表中的每一列都需要明确使用时,而不是编写查询时存在的每一列时,可以使用。数据库将在内部将*扩展为完整的列列表-没有性能差异。

否则,显式列出要在查询中使用的每一列-最好在使用表别名时。


20

即使您现在要选择每一列,在某人添加一个或多个新列之后,您可能也不想选择每一列。如果与SELECT *您一起编写查询,则冒着冒险,有时某人可能会添加一列文本,这使您的查询运行得更慢,即使您实际上不需要该列也是如此。

如果添加了想要的新列,这是否意味着更少的代码更改?

如果您确实想使用新列,那么无论如何您都将不得不对代码进行很多其他更改。您仅在保存, new_column-只需输入几个字符即可。


21
特别是如果该新列是一个三兆字节的BLOB
Matti Virkkunen 2010年

2
@Matti-但希望他们会比“嘿,让巨大的BLOB列放到这张桌子上!”的想法更多(是个傻瓜希望我知道,但是一个男人不能做梦吗?)
ChaosPandion 2010年

5
性能是一个方面,但通常还有一个正确性方面:投影结果的形状*可能会意外更改,这可能会对应用程序本身造成严重破坏:序数引用的列(例如sqldatareader.getstring(2))突然检索到在不同的列中,任何列INSERT ... SELECT *都会中断,依此类推。
Remus Rusanu

2
@chaos:将blob放在表上并不会真正损害您的性能...除非您使用SELECT * ... ;-)
Dave Markle 2010年

2
在性能引起实际问题之前,您不必担心性能。而且,SELECT *节省几个字符也不是问题。这是节省调试时间的问题,因为很容易忘记指定新添加的列。
刘易斯

4

如果在SELECT语句中命名列,则将按指定的顺序返回它们,因此可以安全地由数字索引引用。如果使用“ SELECT *”,则最终可能会以任意顺序接收列,因此只能安全地按名称使用列。除非您事先知道要对添加到数据库中的任何新列要做什么,否则最可能的正确操作是忽略它。如果您将忽略添加到数据库中的任何新列,则检索它们没有任何好处。


“因此可以安全地由数字索引引用”,但是谁会愚蠢到曾经尝试用数字索引而不是名称来引用列!与在视图中使用select *相比,反模式要糟糕得多。
MGOwen '16

@MGOwen:使用select *索引然后再使用列会很可怕,但是使用select X, Y, Zselect A,B,C然后将结果数据读取器传递给期望对列0、1和2中的数据进行处理的代码似乎是一种完全合理的方法允许相同的代码作用于X,Y,Z或A,B,C。请注意,列的索引将取决于它们在SELECT语句中的位置,而不是它们在数据库中的顺序。
超级猫

3

在很多情况下,SELECT *会在应用程序的运行时而不是设计时导致错误。它隐藏了列更改或应用程序中错误引用的知识。


1
那么命名列有什么帮助呢?在SQL Server中,嵌入到代码或SP中的现有查询在运行之前不会抱怨,即使您已为列命名。当测试新的SP时,它们将失败,但是您必须花费大量时间寻找受表更改影响的SP。您指的是在设计时会遇到什么情况?
克里斯·阿里(ChrisA)2010年

3

如果您真的想要每列,那么我在选择(*)和命名列之间并没有看到性能差异。命名列的驱动程序可能只是为了明确说明您希望在代码中看到哪些列。

但是,通常您并不希望每一列,而且select(*)可能导致数据库服务器不必要的工作,并且不必要的信息也必须通过网络传递。除非系统使用率高或网络连接速度慢,否则不太可能引起明显的问题。


3

将其视为减少应用程序与数据库之间的耦合。

总结“代码气味”方面:
SELECT *在应用程序和架构之间创建动态依赖关系。限制其使用是使依赖项更加定义的一种方法,否则对数据库的更改更有可能使应用程序崩溃。


3

如果您将字段添加到表中,它们将自动包含在您使用的所有查询中select *。这似乎很方便,但是当您获取的数据超过您的需要时,它会使您的应用程序变慢,并且实际上会使您的应用程序崩溃。

在结果的每一行中可以获取多少数据是有限制的。如果将字段添加到表中,以致结果最终超出该限制,则在尝试运行查询时会收到错误消息。

这是很难发现的错误。您在一个地方进行更改,而在其他地方却根本没有使用新数据的情况就变了。它甚至可能是一个不那么频繁使用的查询,因此需要一段时间才能被某人使用,这使得将错误与更改联系起来更加困难。

如果指定结果中要包含哪些字段,则可以避免此类开销溢出。



2

从本文引用。

永远不要使用“ SELECT *”,

我发现只有一个理由使用“ SELECT *”

如果您有特殊要求并在创建动态环境时添加或删除列,则由应用程序代码自动处理。在这种特殊情况下,您不需要更改应用程序和数据库代码,这将自动影响生产环境。在这种情况下,您可以使用“ SELECT *”。


1

通常,您必须将结果适合SELECT * ...各种类型的数据结构。如果不指定结果到达的顺序,则将所有内容正确地排列起来可能很棘手(而且更晦涩的字段更容易遗漏)。

这样,您可以出于各种原因将字段添加到表中(即使在表的中间),而不会破坏整个应用程序的sql访问代码。


1

使用SELECT *时,你只需要两列不仅仅意味着你需要传输更多数据。这增加了对数据库的处理,并增加了将数据发送到客户端的延迟。除此之外,它在加载时将使用更多的内存,在某些情况下,例如大的BLOB文件,使用的内存会更多,这主要是关于效率。

但是,除此之外,在查询时更容易查看正在加载的列,而不必查找表中的内容。

是的,如果您确实添加了额外的列,则速度会更快,但是在大多数情况下,您希望/需要使用查询来更改代码以仍然接受新列,并且有可能获得您不希望的列希望/期望会引起问题。例如,如果获取所有列,然后依靠循环中的顺序分配变量,然后再添加一个,否则,如果列顺序发生更改(从备份还原时发现发生了这种情况),则可能会使所有内容丢掉。

这也与为什么要执行操作INSERT时应始终指定列的原因相同。


1

我认为这真的没有总括规则。在许多情况下,我避免使用SELECT *,但我也使用SELECT *非常有用的数据框架。

与所有事物一样,有收益和成本。我认为收益与成本等式的一部分就是您对数据结构的控制程度。在SELECT *运作良好的情况下,数据结构受到严格控制(这是零售软件),因此,有人将一个巨大的BLOB字段收集到表中的风险不大。


1

使用列名进行选择会增加数据库引擎可以从索引访问数据而不是查询表数据的可能性。

SELECT *在数据库架构发生更改的情况下使系统暴露于意外的性能和功能更改,因为您将要向表中添加任何新列,即使您的代码不准备使用或呈现该新数据。


1

还有一个更加务实的理由:金钱。当您使用云数据库并且必须为处理的数据付费时,没有解释要立即丢弃的数据的解释。

例如:BigQuery

查询价格

查询定价是指运行SQL命令和用户定义函数的成本。BigQuery使用一种度量标准对查询收费:已处理的字节数。

控制投影-避免SELECT *

最佳实践:控制投影-仅查询所需的列。

投影是指查询读取的列数。投影多余的列会导致额外的(浪费)I / O和实现(写入结果)。

使用SELECT *是查询数据的最昂贵方法。当您使用SELECT *时,BigQuery会对表中的每一列进行完整扫描。


0

设计架构之前,请先了解您的要求(如果可能)。

了解数据,1)建立索引2)使用的存储类型,3)供应商引擎或功能;即...缓存,内存功能4)数据类型5)表的大小6)查询频率7)资源共享时的相关工作量8)测试

A)要求会有所不同。如果硬件不能支持预期的工作负载,则应重新评估如何在工作负载中提供要求。关于表的添加列。如果数据库支持视图,则可以创建具有特定命名列的特定数据的索引视图(?)(相对于选择“ *”)。定期检查您的数据和架构,以确保您永远不会遇到“垃圾输入”->“垃圾输出”综合症。

假设没有其他解决方案;您可以考虑以下因素。一个问题总是有多种解决方案。

1)索引:select *将执行表格扫描。根据各种因素,这可能涉及磁盘搜索和/或与其他查询的竞争。如果该表是多用途的,请确保所有查询都可以执行并在目标时间以下执行。如果有大量数据,并且您的网络或其他资源没有被调整;您需要考虑到这一点。该数据库是一个共享环境。

2)存储类型。即:如果您使用的是SSD,磁盘或内存。I / O时间和系统/ cpu上的负载会有所不同。

3)DBA可以调整数据库/表以获得更高的性能吗?不管出于什么原因,团队都认为选择'*'是解决问题的最佳方法。可以将数据库或表加载到内存中。(或其他方法...也许响应被设计为以2-3秒的延迟响应?---同时播放广告以赚取公司收入...)

4)从基线开始。了解您的数据类型,以及如何显示结果。较小的数据类型,字段数减少了结果集中返回的数据量。这使资源可用于其他系统需求。系统资源通常有限制;在这些限制之下“始终”工作可确保稳定性和可预测的行为。

5)表/数据的大小。选择'*'在小表中很常见。它们通常适合内存,响应时间很快。再次...查看您的要求。计划特征蠕变;始终针对当前和将来的需求进行规划。

6)查询频率。请注意系统上的其他工作负载。如果此查询每秒触发一次,则表很小。可以将结果集设计为保留在缓存/内存中。但是,如果查询是一个频繁的批处理过程,其中包含千兆字节/千兆字节的数据……您最好分配专用资源以确保其他工作负载不受影响。

7)相关的工作量。了解如何使用资源。网络/系统/数据库/表/应用程序是专用的还是共享的?谁是利益相关者?这是用于生产,开发还是质量检查?这是临时的“快速修复”吗?您是否测试过该方案?您会惊讶于当前的硬件上可能存在多少问题。(是的,性能很快...但是设计/性能仍然下降。)系统是否需要每秒执行10K查询而不是每秒5-10查询。是数据库服务器专用于监视还是在其他应用程序中监视共享资源上的执行。一些应用程序/语言;O / S将消耗100%的内存,从而导致各种症状/问题。

8)测试:测试您的理论,并尽可能多地了解。您选择的“ *”问题可能很重要,或者甚至您不必担心。

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.