昨天我正在与“业余”程序员讨论(我自己是专业程序员)。我们遇到了他的一些工作,他说他总是查询数据库中的所有列(即使在生产服务器上/代码中)。
我试图说服他不要这样做,但是还没有那么成功。在我看来,程序员仅应查询“简洁”,效率和流量方面实际需要的内容。我误会我的观点了吗?
昨天我正在与“业余”程序员讨论(我自己是专业程序员)。我们遇到了他的一些工作,他说他总是查询数据库中的所有列(即使在生产服务器上/代码中)。
我试图说服他不要这样做,但是还没有那么成功。在我看来,程序员仅应查询“简洁”,效率和流量方面实际需要的内容。我误会我的观点了吗?
Answers:
考虑一下您将获得什么,以及如何将它们绑定到代码中的变量。
现在想一想,当有人更新表架构以添加(或删除)一列,甚至是您不直接使用的列时,会发生什么。
手动键入查询时使用select *很好,而不是在编写代码查询时使用。
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=1
,data=42
,twoid=2
,和other=43
。
现在,如果我在表一中添加一列会怎样?http://sqlfiddle.com/#!2/cd0b0/1
alter table one add column other text;
update one set other = 'foo';
而从同一个查询我的结果之前oneid=1
,data=42
,twoid=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 *
寻求所有可能影响性能的方法,您就会继续寻找新方法。
另一个需要注意的问题:如果这是一个JOIN
查询,并且您正在将查询结果检索到关联数组中(如PHP中的情况),则容易出错。
事实是
foo
有列id
和name
bar
包含列id
和address
,SELECT * FROM foo
JOIN bar ON foo.id = bar.id
猜猜有人name
在bar
表中添加一列会发生什么。
该代码将突然停止正常工作,因为该name
列现在在结果中出现两次,并且如果将结果存储到数组中,第二name
(bar.name
)中的数据将覆盖第一个name
(foo.name
)!
这是一个非常讨厌的错误,因为它不是很明显。可能需要花费一些时间才能弄清楚,并且在表中添加另一列的人员不可能预料到这种不良副作用。
(真实的故事)。
因此,不要使用*
来控制要检索的列,并在适当的地方使用别名。
SELECT
子句中添加另一列,这是您希望发现名称不唯一的时候。顺便说一句,我认为在具有大型数据库的系统中这种情况并不罕见。正如我所说,我曾经花了几个小时在大量的PHP代码泥潭中寻找此错误。我刚才发现了另一种情况:stackoverflow.com/q/17715049/168719
在许多情况下,查询每一列可能是完全合法的。
并非总是查询每一列。
数据库引擎需要做更多的工作,数据库引擎必须经过反复讨论,并围绕其内部元数据进行盘算,才能确定它需要处理哪些列,然后才能继续进行实际获取数据并将其发送回给您的实际业务。好的,这不是世界上最大的开销,但是系统目录可能是一个明显的瓶颈。
对于您的网络来说,这需要做更多的工作,因为当您可能只需要一个或两个字段时,您将撤回任意数量的字段。如果有人[else]添加了几十个额外的字段,而这些字段都包含大块的文本,那么您的吞吐量突然就越过地板了-并没有显而易见的原因。如果您的“ where”子句不是特别好,并且您还要拉回很多行,这将变得更糟-这可能会导致大量数据在整个网络上流传至您(即速度很慢)。
对于您的应用程序来说,这是更多的工作,必须撤回并存储它可能根本不在乎的所有这些额外数据。
您冒着改变列顺序的风险。好的,您不必为此担心(如果仅选择所需的列,则不会担心),但是,如果您一次获得所有这些,并且[其他]决定重新安排表中的列顺序,经过精心制作的,您在大厅下进行帐户处理的CSV导出突然全部流传了出来-再次,没有显而易见的原因。
顺便说一句,我在上面说了几次“其他”。请记住,数据库本质上是多用户的。您可能无法控制自己认为可以做到的事情。
TOP
限制;我不确定如果代码读取的内容尽可能多地显示然后处理查询,那么这有多重要。我认为查询响应的处理有些延迟,尽管我不知道细节。无论如何,我认为与其说“不是合法的”,不如说“……合法的要少得多” 会更好;基本上,我将合法案例概括为那些让用户比程序员更了解有意义的案例。
简短的答案是:这取决于他们使用哪个数据库。关系数据库经过优化,可以快速,可靠和原子地提取所需的数据。在大型数据集和复杂查询上,它比SELECT *更快,更安全,并且等效于“代码”端的联接。键值存储可能未实现此类功能,或者可能不够成熟,无法在生产中使用。
也就是说,您仍然可以使用SELECT *填充正在使用的任何数据结构,并在代码中进行处理,但是如果要扩展,则会发现性能瓶颈。
最接近的比较是对数据进行排序:您可以使用quicksort或bubbleort,结果将是正确的。但是不会被优化,当您引入并发并且需要原子排序时,肯定会出现问题。
当然,添加RAM和CPU比投资于可以执行SQL查询甚至对JOIN是什么含糊不清的程序员的投资要便宜。
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脱颖而出。
var customer = _db.Customers.Where(it => it.id == id).First();
。
IMO,关于显式还是隐式。当我编写代码时,我希望它能够工作是因为我使它起作用了,而不仅仅是因为所有部分都恰好在那里。如果您查询所有记录并且您的代码有效,那么您将有继续前进的趋势。后来,如果发生了什么变化,现在您的代码不起作用,调试大量查询和函数以查找应该存在的值是唯一的麻烦,唯一的值引用是*。
同样,在N层方法中,最好还是将数据库模式中断隔离到数据层。如果您的数据层正在将*传递给业务逻辑,并且很可能在表示层上传递,则您的调试范围将成倍扩大。
select *
要差得多!
除了开销之外,您首先要避免的事情是,我想说,作为程序员,您并不依赖于数据库管理员定义的列顺序。即使您需要全部,也可以选择每个列。
我看不出为什么不应该出于构建它的目的使用任何理由-从数据库中检索所有列。我看到三种情况:
在数据库中添加了一列,并且您也希望在代码中使用它。a)带*将失败,并显示正确的消息。b)不带*将起作用,但不会执行您期望的那样,这是非常糟糕的。
在数据库中添加了一个列,您不希望在代码中使用它。a)带*将失败;这意味着*不再适用,因为它的语义意味着“全部检索”。b)不带*将起作用。
删除列无论哪种方式,代码都将失败。
现在最常见的情况是情况1(因为您使用*,这表示您最有可能想要全部)。没有*的话,您的代码可以正常运行,但不能达到预期的效果,而该错误会导致代码错误并显示正确的错误消息,这要糟得多。
我没有考虑基于列索引检索列数据的代码,我认为这很容易出错。根据列名检索它的逻辑要多得多。
Select *
旨在为临时查询提供更多便利,而不是出于应用程序开发目的。或用于统计构造中,例如select count(*)
让查询引擎决定是否使用索引,使用哪个索引等等,而您不返回任何实际的列数据。或用于类似的子句中where exists( select * from other_table where ... )
,这再次邀请查询引擎自行选择最有效的路径,子查询仅用于约束主查询的结果。等等
select *
具有检索所有列的语义。如果您的应用程序确实需要此功能,我看不出为什么不使用它的任何原因。您能否指出一些参考(Oracle,IBM,Microsoft等),提及其select *
构建目的不是检索所有列?
select *
存在的是检索所有列...作为一种方便功能,用于临时查询,不是因为它在生产软件中是个好主意。该页面的答案已经很好地说明了原因,这就是为什么我没有创建自己的详细答案的原因:•)性能问题,通过网络反复编组您从未使用过的数据,•)列别名问题, •)查询计划优化故障(故障在某些情况下使用索引),•)低效的服务器I /案件O,其中有限选择可能仅使用索引等
select *
在实际的生产应用程序中使用它是合理的,但是边缘案例的本质是它不是常见的案例。:-)
select *
; 我说的是如果您真的需要所有列,我看不出您不应该使用的理由select *
;尽管很少有需要所有列的场景。
这样想吧...如果您从一个只有几个小字符串或数字字段的表中查询所有列,则总共有100k数据。不好的做法,但它会执行。现在,添加一个字段,其中包含图像或10mb的word文档。现在,您快速执行的查询立即神秘地开始表现不佳,仅是因为将字段添加到了表中……您可能不需要那个庞大的数据元素,但是Select * from Table
无论如何您都已经做到了。