为什么SELECT *不良做法?

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

评论

SELECT COUNT(*)太差了,而且过时了。有关SELECT的信息*-请参阅:stackoverflow.com/questions/1960036/…

SELECT COUNT(*)给出与SELECT COUNT(SomeColumn)不同的答案,除非该列是NOT NULL列。并且优化程序可以给予SELECT COUNT(*)特殊待遇-通常是这样。另请注意,WHERE EXISTS(SELECT * FROM SomeTable WHERE ...)被给予特殊情况处理。

@Michael Mrozek,实际上是问题的反面。我在问它是否有害,而不是有害。

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

对于SQL Server,请参见sqlblog.com/blogs/aaron_bertrand/archive/2009/10/10/…

#1 楼

确实有三个主要原因:


将数据转移到消费者的效率低下。当您选择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是正确的。)



评论


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

–MichałTatarynowicz
2010-09-4 6:38



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

–讨厌
2012年10月20日在22:11



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

–戴夫·马克尔
2012年10月22日,下午2:27

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

– Ben D
13年5月29日在21:32

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

– B
19年2月2日,下午3:07

#2 楼

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

性能

*速记可能会变慢,原因是:


并非所有字段都已索引,因此强制进行全表扫描-效率较低
通过网络发送SELECT *所保存的内容可能会受到全表扫描的威胁
返回的数据量超过所需的数据
使用可变长度数据类型返回尾随列可能会导致搜索开销

维护

使用SELECT *时:


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

设计

SELECT *是一个反模式:


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

何时应该使用“ SELECT *”?

当明确需要涉及表中的每一列而不是每列时,可以使用SELECT *编写查询时存在的列。数据库将在内部将*扩展为完整的列列表-没有性能差异。否则,显式列出要在查询中使用的每一列-最好是在使用表别名时。 br />

#3 楼

即使您现在要选择每一列,也可能在有人添加一个或多个新列之后不想选择每一列。如果使用SELECT *编写查询,则可能会冒着风险,有时您可能会添加一列文本,即使您实际上不需要该列,也会使查询运行得更慢。 br />如果添加了想要的新列,这是否意味着更少的代码更改?


机会是,如果您实际上想使用新的列,那么您将拥有无论如何都要对代码进行很多其他更改。您只保存, new_column-只需输入几个字符即可。

评论


尤其是如果该新列是三兆字节的BLOB

–马蒂(Matti Virkkunen)
2010-09-3 22:05

@Matti-但希望他们会比“嘿,让巨大的BLOB列放到这张桌子上!”要多加思考。 (是的,一个傻瓜希望我知道,但是一个人不能做梦吗?)

–混沌潘迪翁
2010-09-3 22:35



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

–雷木斯·鲁萨努(Remus Rusanu)
2010-09-3 22:57

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

–戴夫·马克尔
10-9-4'2:03

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

–刘易斯
16-3-22在12:08

#4 楼

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

评论


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

– MGOwen
16-09-19在9:28

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

–超级猫
16-9-19在14:11

#5 楼

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

评论


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

– ChristA
2010-09-3 22:30



#6 楼

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

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

#7 楼

可以认为这是减少应用程序与数据库之间的耦合。

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

#8 楼

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

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

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

如果您在结果,您可以避免此类开销溢出。

评论


对于表值函数,实际上不是真的将新字段包含在查询中

– Mulllhausen
18年7月6日在2:43



#9 楼



从不使用“ SELECT *”,

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

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

#10 楼

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

通过这种方式,您可以将字段添加到表中(甚至由于各种原因而没有在整个应用程序中破坏sql访问代码。

#11 楼

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

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

是的,如果确实添加了额外的列,这样做会更快,但是在大多数情况下, d无论如何都希望使用查询来更改代码以接受新列,并且有可能获取不需要/期望的列会导致问题。例如,如果您抓住所有列,然后依靠循环中的顺序分配变量,然后再添加一个,否则,如果列顺序发生更改(从备份中还原时会发生这种情况),则可能会丢掉所有内容。 br />
这也与为什么要执行INSERT时应始终指定列的原因相同。

#12 楼

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

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

#13 楼

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

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

#14 楼

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

例如:BigQuery:


查询定价

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


和控制投影-避免使用SELECT *:


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

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

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


#15 楼

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

了解数据,
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查询。是数据库服务器专用于监视还是在其他应用程序上监视共享资源上的执行。一些应用程序/语言;操作系统将消耗100%的内存,从而导致各种症状/问题。

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