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

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

评论

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

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

SO的充分理由-stackoverflow.com/questions/3180375/select-vs-select-column

这里有充分的理由,为什么选择*被认为有害?

@gnat一个问题真的可以认为是一个封闭问题的重复吗? (即因为封闭的容器最初并不十分适合)

#1 楼

考虑一下您要返回的内容,以及如何将它们绑定到代码中的变量。

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

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

评论


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

– jwenting
2014年4月3日14:38

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

– gbjbaanb
2014年4月3日14:49

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

–user1068
2014年4月3日15:54



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

– TallGuy
2014年4月3日在19:26

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

–user253751
2014年4月3日在22:14

#2 楼

模式更改


按顺序获取---如果代码获取列#作为获取数据的方式,则模式更改将导致列号重新调整。
按名称获取---如果代码按名称获取列(例如foo),并且查询中的另一个表添加列foo,则此处理方式尝试获取正确的foo列时可能会导致问题。

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

进一步考虑是否有一个列正在使用的表从表中删除。 select * from ...仍然可以工作,但是尝试从结果集中提取数据时出错。如果在查询中指定了列,则查询将出错,而不是给出问题的根源和位置的明确指示。

数据开销

某些列可以具有与它们相关的大量数据。选择*将拉回所有数据。是的,这就是您选择的1000行上的varchar(4096),它为您提供了您可能不需要的4兆字节额外数据,但无论如何都是通过网络发送的。

模式更改时,第一次创建表时varchar可能不存在,但是现在在那里。但只需要其中的2个,就没有传达代码的意图。当查看执行*的查询时,不知道查询的重要部分是什么。我可以更改查询以使用其他计划,而不通过不包含这些列来使其更快吗?我不知道,因为查询返回的意图尚不清楚。


让我们看一些探索这些模式更改的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);


返回的列是select *oneid=1data=42以及twoid=2

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

alter table one add column other text;

update one set other = 'foo';


和以前相同的查询结果是other=43oneid=1data=42twoid=2

其中一个表中的更改中断了other=foo的值,突然您将“ other”与int的绑定将引发错误,并且您不知道为什么。 >
如果相反,您的SQL语句是

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


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


索引

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

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

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

如果只是选择,一个用户的姓(您需要执行很多操作,因此要有一个索引),数据库可以执行仅索引扫描(仅Postgres Wiki索引扫描,mysql全表扫描与全索引扫描,仅索引扫描):避免表访问)。

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

这并不是说完整的索引扫描很棒,但仍然全扫描-但比全表扫描更好。一旦开始追逐select *会影响性能的所有方式,您就会不断发现新的方式。

相关阅读内容


关于在SQL中正确使用*通配符的困惑
(堆栈溢出):select *与选择列

(堆栈溢出):为什么SELECT *被认为有害?



评论


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

– gbjbaanb
2014年4月4日在7:28

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

–朱莉娅·海沃德(Julia Hayward)
2014年4月4日在8:51



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

–Tonny
14年4月4日在9:01

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

–user40980
2014年4月7日,0:59



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

–李本
2014年4月8日在16:25



#3 楼

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

问题在于,如果表foo具有列idname
如果表bar具有列idaddress
和在您的代码中,您正在使用SELECT * FROM foo JOIN bar ON foo.id = bar.id

猜测当有人在name表中添加一列bar时会发生什么。

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

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

(真实的故事)。

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

评论


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

–培根
2014年4月3日在12:36

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

– Konrad Morawski
2014年4月3日12:40



上周我花了一个小时试图通过一名顾问团长解决这个问题。他应该是一个SQL专家。。。

–Tonny
2014年4月3日在22:30

#4 楼

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

并非总是查询每列。

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

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

您的应用程序需要做更多的工作,必须撤回并存储所有可能不需要的额外数据。

冒着改变列顺序的风险。好的,您不必为此担心(如果仅选择所需的列,则不会担心),但是,如果您一次获得所有这些列,并且[其他]决定重新安排表中的列顺序,经过精心制作的CSV导出文件,您突然下了帐,到了锅里,这又再次变得毫无意义。

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

评论


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

–超级猫
2014年4月3日在15:11

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

–Tonny
2014年4月3日在22:39

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

–超级猫
2014年4月3日22:44

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

–Tonny
2014年4月3日在22:49

#5 楼

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

话虽如此,您仍然可以使用SELECT *填充正在使用的任何数据结构并进行计算其余的在代码中,但是如果您想扩展,就会发现性能瓶颈。

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

评论


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

– Craig
2014年4月5日下午13:23

@Craig:SQL在广泛的关系数据库中很常见。但是,这远非唯一的数据库类型,而且...有一个原因,更现代的数据库方法通常被称为NoSQL。 :P我知道,没有人会在没有大量讽刺意味的情况下将SQL称为“优雅”。就关系数据库而言,它仅比许多替代方案少。

– cHao
2014年4月6日在8:27

@cHao我几十年来一直非常了解各种其他类型的数据库。选择“ nosql”数据库已经存在了很长时间。 “ NoSQL”甚至不是一个新概念。 ORM也一直存在,而且一直都很慢。慢!=好。至于优雅(LINQ?),您不能说服这对于where子句来说是合理的还是优雅的:Customer customer = this._db.Customers.Where(“ it.ID = @ID”,新的ObjectParameter(“ ID” ,id)).First();请参阅第2页的冒犯时间。

– Craig
2014年4月6日在18:38

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

– cHao
2014年4月6日19:06



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

– cHao
2014年4月6日19:16



#6 楼

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

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

评论


这可能是这里最重要的原因之一,而且票数很少。带有select *的代码库的可维护性要差得多!

–Eamon Nerbonne
2014年4月5日上午9:39

#7 楼

因为如果表中有新的列,那么即使不需要它们,您也会得到所有这些列。使用varchars,这可能会变成大量需要从数据库传输的额外数据

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

#8 楼

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

评论


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

–罗里·亨特(Rory Hunter)
2014年4月3日14:05

借调,进行。使用列名,而不依赖于列顺序。列顺序是脆弱的依赖关系。这些名称应该(您希望)是从一些实际的设计工作中派生的,或者您在查询中显式地别名复合列或计算或冲突的列名称,并引用您指定的显式别名。但是仅仅依靠胶带和祈祷就可以依靠秩序...

– Craig
2014年4月5日13:18

#9 楼

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


在数据库中添加了一个列,您也希望在代码中使用它。
a)带有*的行将失败,并显示一条正确的消息。
b)带有*的行将有效,但不会执行您期望的那样,这很糟糕。
在数据库中添加了一个列,您不希望在代码中使用它。
a)带有*的将失败;这意味着*不再适用,因为它的语义意味着“全部检索”。
b)如果没有*将起作用。
删除列的方法
两种方法都会失败。

现在,最常见的情况是情况1(因为您使用*表示您很可能想要全部)。没有*的话,您的代码可以正常运行,但不能达到预期的效果,这会导致代码错误并失败,导致更糟糕的结果。

我没有考虑那些根据我认为容易出错的列索引检索列数据。根据列名检索它的逻辑要多得多。

评论


您的前提不正确。 Select *的目的更多是为了方便即席查询,而不是出于应用程序开发目的。或用于统计构造(如select count(*)),该统计构造使查询引擎可以决定是否使用索引,要使用哪个索引等等,并且您不会返回任何实际的列数据。或用于where存在的子句(select from from other_table where ...),这再次邀请查询引擎自行选择最有效的路径,而子查询仅用于约束主查询的结果。等等。

– Craig
2014年4月10日,凌晨3:14

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

–随机42
2014-04-10 6:18



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

– Craig
2014年4月10日14:44



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

– Craig
2014年4月10日15:01

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

–随机42
14年4月10日在16:45

#10 楼

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

评论


这似乎只是重复几个小时前在第一个答案和其他几个答案中已经提出的观点

– gna
2014年4月3日19:02