为什么“从表中选择*”被视为不良做法


96

昨天我正在与“业余”程序员讨论(我自己是专业程序员)。我们遇到了他的一些工作,他说他总是查询数据库中的所有列(即使在生产服务器上/代码中)。

我试图说服他不要这样做,但是还没有那么成功。在我看来,程序员仅应查询“简洁”,效率和流量方面实际需要的内容。我误会我的观点了吗?


1
我要说的是,如果表的内容改变了怎么办?添加/删除列?您仍在选择* ..,因此您将丢失某些东西或提取超出您需要的数据。
JF,

2
@JFit这是其中的一部分,但远非全部。
jwenting 2014年



@gnat一个问题真的可以认为是一个封闭问题的重复吗?(即,因为封闭的对象最初并不很适合)
gbjbaanb 2014年

Answers:


67

考虑一下您将获得什么,以及如何将它们绑定到代码中的变量。

现在想一想,当有人更新表架构以添加(或删除)一列,甚至是您不直接使用的列时,会发生什么。

手动键入查询时使用select *很好,而不是在编写代码查询时使用。


8
性能,网络负载等等等远比按顺序返回所需名称的列要方便得多。
jwenting 2014年

21
@jwenting真的吗?性能比正确性更重要吗?无论如何,我没有看到“选择*”比仅选择所需的列更好。
gbjbaanb 2014年

9
@Bratch,在现实生活的生产环境中,您可能有数百个使用相同表的应用程序,并且不可能正确地维护所有这些应用程序。您的观点是正确的,但是实际上,由于在实际工作中的实际情况,该论点失败了。对活动表的架构更改始终发生。
user1068'4

18
我不明白这个答案的意义。如果将列添加到表中,则SELECT *和SELECT [Columns]都将起作用,唯一的区别是,如果代码需要绑定到新列,则需要修改SELECT [Columns],而SELECT *不会。如果从表中删除了列,则SELECT *在绑定点将中断,而SELECT [Columns]在执行查询时将中断。在我看来,SELECT *是更灵活的选项,因为对表的任何更改仅需要更改绑定。我想念什么吗?
TallGuy 2014年

11
@gbjbaanb然后按名称访问列。除非您在查询中指定了列顺序,否则其他任何事情显然都是愚蠢的。
immibis 2014年

179

模式变更

  • 按顺序获取---如果代码正在获取列号作为获取数据的方式,则架构中的更改将导致列号重新调整。这会弄乱应用程序,并会发生不好的事情。
  • 按名称获取---如果代码按名称获取列,例如foo,并且查询中的另一个表添加了列foo,则在尝试获取正确的 foo列时,这种处理方式可能会导致问题。

无论哪种方式,架构更改都可能导致数据提取出现问题。

进一步考虑是否从表中删除了正在使用的列。在select * from ...试图拉出来的数据结果集的时候仍然有效,但失误了。如果在查询中指定了该列,则查询将出错,而不是给出有关问题所在和位置的明确指示。

数据开销

一些列可能具有与之关联的大量数据。选择返回*将拉出所有数据。是的,varchar(4096)这就是您选择的1000行上的多数民众赞成,从而为您提供了额外可能需要的4兆字节数据,这些数据无论如何都要通过网络发送。

与模式更改相关,当您第一次创建表时,该varchar可能不存在,但现在在那里。

无法传达意图

当您选择返回*并获得20列,但只需要其中2列时,您并没有传达代码的意图。当看一个查询时,并不select *知道查询的重要部分是什么。我可以更改查询以使用其他计划,而不通过不包含这些列来使其更快吗?我不知道,因为查询返回的意图尚不清楚。


让我们看一些探索这些模式更改的SQL小提琴。

首先,初始数据库:http : //sqlfiddle.com/#!2/a67dd/1

DDL:

create table one (oneid int, data int, twoid int);
create table two (twoid int, other int);

insert into one values (1, 42, 2);
insert into two values (2, 43);

SQL:

select * from one join two on (one.twoid = two.twoid);

而你找回列oneid=1data=42twoid=2,和other=43

现在,如果我在表一中添加一列会怎样?http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

而从同一个查询我的结果之前oneid=1data=42twoid=2,和other=foo

其中一个表的更改中断了a的值,select *突然您将“ other”与int的绑定将引发错误,并且您不知道为什么。

如果相反,您的SQL语句是

select 
    one.oneid, one.data, two.twoid, two.other
from one join two on (one.twoid = two.twoid);

表一的更改不会破坏您的数据。该查询在更改之前和更改之后运行相同。


索引编制

当您执行a时,select * from您将从符合条件的所有表中提取所有行。甚至您根本不在乎的桌子。虽然这意味着要传输更多的数据,但另一个性能问题却潜伏在堆栈的下方。

索引。(与SO有关:如何在select语句中使用索引?

如果要撤回很多列,则数据库计划优化器可能会忽略使用索引,因为无论如何您仍然需要获取所有这些列,并且使用索引然后获取查询中的所有列将花费更多时间。而不是仅仅进行完整的表扫描。

如果您只是选择用户的姓氏(您需要做很多事情,因此要有一个索引),则数据库可以执行仅索引扫描(仅Postgres Wiki索引扫描mysql full table scan vs full索引扫描仅索引扫描:避免表访问)。

如果可能的话,有很多关于仅从索引读取的优化。可以在每个索引页上更快地提取信息,因为您也可以提取更少的信息-您无需为引入所有其他列select *。仅索引扫描可能以 100倍的速度返回结果(来源:Select *是bad)。

这并不是说全索引扫描很棒,它仍然是全扫描,但比全表扫描要好。一旦您开始select *寻求所有可能影响性能的方法,您就会继续寻找新方法。

相关阅读


2
@Tonny我同意-但是当我回答(第一次)时,我从未想到这个问题会引起如此多的讨论和评论!仅查询命名列是不是?!
gbjbaanb 2014年

3
通过添加列来破坏所有内容也是一个很好的理由,为什么代码应始终按名称访问数据读取器中的列,而不是按硬编码顺序...
Julia Hayward

1
@gbjbaanb是我的。但是很多人没有正式的背景/培训就开始编写SQL查询。对他们来说可能并不明显。
Tonny

1
@Aaronaught我已经对索引问题进行了补充。还有其他我要提出的错误观点select *吗?

3
哇,这个被接受的答案在解释任何事情上都太差劲了,因此我拒绝了。惊讶的是这不是公认的答案。+1。
本李

38

另一个需要注意的问题:如果这是一个JOIN查询,并且您正在将查询结果检索到关联数组中(如PHP中的情况),则容易出错。

事实是

  1. 如果表foo有列idname
  2. 如果表格bar包含列idaddress
  3. 在您的代码中,您正在使用 SELECT * FROM foo JOIN bar ON foo.id = bar.id

猜猜有人namebar表中添加一列会发生什么。

该代码将突然停止正常工作,因为该name列现在在结果中出现两次,并且如果将结果存储到数组中,第二namebar.name)中的数据将覆盖第一个namefoo.name)!

这是一个非常讨厌的错误,因为它不是很明显。可能需要花费一些时间才能弄清楚,并且在表中添加另一列的人员不可能预料到这种不良副作用。

(真实的故事)。

因此,不要使用*来控制要检索的列,并在适当的地方使用别名。


好的,在这种情况下(我认为这种情况很少见),这可能是一个主要问题。但是您仍然可以通过使用通配符查询来避免(大多数人可能会避免)它,而只需为相同的列名添加别名即可。
培根2014年

4
从理论上讲,但是如果为了方便起见使用通配符,则依靠它可以自动为您提供所有存在的列,并且永远不会随着表的增长而更新查询。如果要指定每一列,则必须转到查询以在SELECT子句中添加另一列,这是您希望发现名称不唯一的时候。顺便说一句,我认为在具有大型数据库的系统中这种情况并不罕见。正如我所说,我曾经花了几个小时在大量的PHP代码泥潭中寻找此错误。我刚才发现了另一种情况:stackoverflow.com/q/17715049/168719
Konrad Morawski 2014年

3
上周我花了一个小时试图通过咨询顾问团长解决这个问题。他应该是一个SQL专家...感叹...
Tonny 2014年

22

在许多情况下,查询每一列可能是完全合法的。

并非总是查询每一列。

数据库引擎需要做更多的工作,数据库引擎必须经过反复讨论,并围绕其内部元数据进行盘算,才能确定它需要处理哪些列,然后才能继续进行实际获取数据并将其发送回给您的实际业务。好的,这不是世界上最大的开销,但是系统目录可能是一个明显的瓶颈。

对于您的网络来说,这需要做更多的工作,因为当您可能只需要一个或两个字段时,您将撤回任意数量的字段。如果有人[else]添加了几十个额外的字段,而这些字段都包含大块的文本,那么您的吞吐量突然就越过地板了-并没有显而易见的原因。如果您的“ where”子句不是特别好,并且您还要拉回很多行,这将变得更糟-这可能会导致大量数据在整个网络上流传至您(即速度很慢)。

对于您的应用程序来说,这是更多的工作,必须撤回并存储它可能根本不在乎的所有这些额外数据。

您冒着改变列顺序的风险。好的,您不必为此担心(如果仅选择所需的列,则不会担心),但是,如果您一次获得所有这些,并且[其他]决定重新安排表中的列顺序,经过精心制作的,您在大厅下进行帐户处理的CSV导出突然全部流传了出来-再次,没有显而易见的原因。

顺便说一句,我在上面说了几次“其他”。请记住,数据库本质上是多用户的。您可能无法控制自己认为可以做到的事情。


3
我认为始终查询每一列对于诸如与模式无关的表查看工具之类的东西都是合法的。这不是非常普遍的情况,但是在仅内部使用的工具的上下文中,此类事情可能很方便。
supercat 2014年

1
@supercat这只是我能想到的“ SELECT *”的唯一有效用例。而且即使那样,我还是希望将查询限制为“ SELECT TOP 10 *”(在MS SQL中)或添加“ LIMIT 10”(mySQL)或添加“ WHERE ROWNUM <= 10”(Oracle)。通常,在这种情况下,与其说“完整的内容”,不如说是“有多少列和一些样本数据”。
Tonny 2014年

@Tonny:SQL Server更改了其默认脚本以添加TOP限制;我不确定如果代码读取的内容尽可能多地显示然后处理查询,那么这有多重要。我认为查询响应的处理有些延迟,尽管我不知道细节。无论如何,我认为与其说“不是合法的”,不如说“……合法的少得多” 会更好;基本上,我将合法案例概括为那些让用户比程序员更了解有意义的案例。
supercat 2014年

@supercat我可以同意。我真的很喜欢您在最后一句话中说的方式。我必须记住那个。
Tonny 2014年

11

简短的答案是:这取决于他们使用哪个数据库。关系数据库经过优化,可以快速,可靠和原子地提取所需的数据。在大型数据集和复杂查询上,它比SELECT *更快,更安全,并且等效于“代码”端的联接。键值存储可能未实现此类功能,或者可能不够成熟,无法在生产中使用。

也就是说,您仍然可以使用SELECT *填充正在使用的任何数据结构,并在代码中进行处理,但是如果要扩展,则会发现性能瓶颈。

最接近的比较是对数据进行排序:您可以使用quicksort或bubbleort,结果将是正确的。但是不会被优化,当您引入并发并且需要原子排序时,肯定会出现问题。

当然,添加RAM和CPU比投资于可以执行SQL查询甚至对JOIN是什么含糊不清的程序员的投资要便宜。


学习SQL!没那么难。它是广泛的数据库“本机”语言。功能强大。很优雅 它经受了时间的考验。而且,除非您真的不擅长执行SQL联接,否则您不可能在“代码”端编写比数据库联接更高效的联接。考虑到为了进行“代码联接”,即使是简单的2表联接,也必须从两个表中提取所有数据。还是您在获取索引统计信息并使用这些统计信息来决定在加入之前要提取哪些表数据?没这么认为...人们学会正确使用数据库。
克雷格

@克雷格:SQL在关系数据库中非常普遍。但是,这远非唯一的数据库类型,而且...有一个原因,更现代的数据库方法通常被称为NoSQL。:P我没有人知道,如果没有大量讽刺意味的话,它会称呼SQL为“优雅”。就关系数据库而言,它仅比许多替代方法少。
cHao 2014年

@cHao我几十年来一直很清楚各种其他类型的数据库。选择“ nosql”数据库已经存在了很长时间。“ NoSQL”甚至不是一个新概念。ORM也一直存在,而且一直都很慢。慢!=好。至于优雅(?LINQ),你无法说服我,这是合理的还是高雅的where子句:Customer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();时间采取进攻第2页
克雷格

@Craig:甚至不让我开始使用ORM。几乎那里的每个系统都可怕地执行此操作,并且抽象泄漏到处都是。这是因为关系数据库记录不是对象,充其量是对象的一部分的可序列化胆量。但是对于LINQ,您真的要去那里吗?SQLish等效项类似于var cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();....,然后继续从每一行创建一个Customer。LINQ脱颖而出。
cHao 2014年

@Craig:当然,它没有它应该的那么优雅。但是它永远不会像我想要的那样优雅,直到它可以将.net代码转换为SQL。:)这时您可以说var customer = _db.Customers.Where(it => it.id == id).First();
cHao 2014年

8

IMO,关于显式还是隐式。当我编写代码时,我希望它能够工作是因为我使它起作用了,而不仅仅是因为所有部分都恰好在那里。如果您查询所有记录并且您的代码有效,那么您将有继续前进的趋势。后来,如果发生了什么变化,现在您的代码不起作用,调试大量查询和函数以查找应该存在的值是唯一的麻烦,唯一的值引用是*。

同样,在N层方法中,最好还是将数据库模式中断隔离到数据层。如果您的数据层正在将*传递给业务逻辑,并且很可能在表示层上传递,则您的调试范围将成倍扩大。


3
这可能是这里最重要的原因之一,而且票数很少。乱七八糟的代码库的可维护性select *要差得多!
Eamon Nerbonne 2014年

6

因为如果表中有新的列,那么即使不需要它们,您也会得到所有这些列。与varchars这可以成为很多额外的数据,需要从DB旅行

某些数据库优化可能还会将非固定长度记录提取到单独的文件中,以加快对固定长度部分的访问,使用select *会破坏该目的


1

除了开销之外,您首先要避免的事情是,我想说,作为程序员,您并不依赖于数据库管理员定义的列顺序。即使您需要全部,也可以选择每个列。


3
同意,尽管在任何情况下我都建议从列名中提取结果集的值。
罗里·亨特

借调,进行。使用列名,而不依赖于列顺序。列顺序是易碎的依赖项。名称(应该希望)是从一些实际的设计工作中派生的,或者您在查询中显式地别名复合列或计算或冲突的列名称,并引用您指定的显式别名。但是,依靠命令几乎只是胶带和祈祷……
Craig 2014年

1

我看不出为什么不应该出于构建它的目的使用任何理由-从数据库中检索所有列。我看到三种情况:

  1. 在数据库中添加了一列,并且您也希望在代码中使用它。a)带*将失败,并显示正确的消息。b)不带*将起作用,但不会执行您期望的那样,这是非常糟糕的。

  2. 在数据库中添加了一个列,您不希望在代码中使用它。a)带*将失败;这意味着*不再适用,因为它的语义意味着“全部检索”。b)不带*将起作用。

  3. 删除列无论哪种方式,代码都将失败。

现在最常见的情况是情况1(因为您使用*,这表示您最有可能想要全部)。没有*的话,您的代码可以正常运行,但不能达到预期的效果,而该错误会导致代码错误并显示正确的错误消息,这要糟得多

我没有考虑基于列索引检索列数据的代码,我认为这很容易出错。根据列名检索它的逻辑要多得多。


您的前提不正确。Select *旨在为临时查询提供更多便利,而不是出于应用程序开发目的。或用于统计构造中,例如select count(*)让查询引擎决定是否使用索引,使用哪个索引等等,而您不返回任何实际的列数据。或用于类似的子句中where exists( select * from other_table where ... ),这再次邀请查询引擎自行选择最有效的路径,子查询仅用于约束主查询的结果。等等
Craig

@Craig我相信每本有关SQL的书/教程都说select *具有检索所有列的语义。如果您的应用程序确实需要此功能,我看不出为什么不使用它的任何原因。您能否指出一些参考(Oracle,IBM,Microsoft等),提及其select *构建目的不是检索所有列?
m3th0dman'4

好吧,当然select *存在的是检索所有列...作为一种方便功能,用于临时查询,不是因为它在生产软件中是个好主意。该页面的答案已经很好地说明了原因,这就是为什么我没有创建自己的详细答案的原因:•)性能问题,通过网络反复编组您从未使用过的数据,•)列别名问题, •)查询计划优化故障(故障在某些情况下使用索引),•)低效的服务器I /案件O,其中有限选择可能使用索引等
克雷格

也许在这里或那里有一个边缘案例可以证明select *在实际的生产应用程序中使用它是合理的,但是边缘案例的本质是它不是常见的案例。:-)
Craig

@Craig的原因是反对从数据库中检索所有列而不反对使用select *; 我说的是如果您真的需要所有列,我看不出您不应该使用的理由select *;尽管很少有需要所有列的场景。
m3th0dman 2014年

1

这样想吧...如果您从一个只有几个小字符串或数字字段的表中查询所有列,则总共有100k数据。不好的做法,但它会执行。现在,添加一个字段,其中包含图像或10mb的word文档。现在,您快速执行的查询立即神秘地开始表现不佳,仅是因为将字段添加到了表中……您可能不需要那个庞大的数据元素,但是Select * from Table无论如何您都已经做到了。


6
这似乎只是重复几个小时前在第一个答案和其他几个答案中已经提出的观点
gnat 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.