选择*与选择列


124

如果我只需要2/3列,而是查询SELECT *而不是在select查询中提供这些列,那么关于更多/更少I / O或内存的性能是否会降低?

如果我不需要选择*,则可能会出现网络开销。

但是在选择操作中,数据库引擎是否总是从磁盘中提取原子元组,还是仅提取在选择操作中请求的那些列?

如果它总是拉一个元组,那么I / O开销是相同的。

同时,如果它拉出一个元组,从元组中剥离请求的列可能会占用内存。

因此,在这种情况下,select someColumn将比select *具有更多的内存开销。


您是否在询问特定的RDBMS?这有可能是如何SELECT查询执行/处理是从数据库到数据库中的不同。
冒犯君主

10
CREATE VIEW foo_view AS SELECT * FROM foo;顺便说一句,在PostgreSQL中,如果您说,然后稍后将列添加到表foo中,则这些列将不会按预期自动显示在foo_view中。换句话说,*在此上下文中,只能扩展一次(在视图创建时),而不是每个SELECT扩展一次。由于ALTER TABLE带来的复杂性,我想(在实践中)*被认为是有害的。
乔伊·亚当斯

@JoeyAdams-不仅是PostgresQL,这也是Oracle的行为。
APC 2010年

1
@OMG小马:我不知道类似的帖子。但是,这些并不是真正的模仿。@Lèsemajesté:我说的是通用RDBMS。与任何特定的供应商无关@Joey Adams:嗯,我知道*是不安全的。只想讨论有关性能的问题。
Neel Basu 2010年

Answers:


31

它总是拉一个元组(表被垂直分割的情况除外-分成几列),因此,要回答您提出的问题,从性能的角度来看并不重要。但是,由于许多其他原因,(在下面)您应始终按名称专门选择想要的那些列。

它总是拉一个元组,因为(在我熟悉的每个供应商RDBMS中),所有内容(包括表数据)的底层磁盘存储结构都基于定义的 I / O页(在SQL Server中,例如,每个Page 8 KB)。每个I / O读或写都是按页进行的。即,每个写或读都是完整的数据页。

由于存在这种潜在的结构限制,结果是数据库中的每一行数据必须始终位于一页上。它不能跨越多个数据页(除了blob之类的特殊事物外,实际的blob数据存储在单独的分页块中,而实际的表行列则仅获得一个指针...)。但是这些例外只是例外,通常不适用,除非在特殊情况下(对于特殊类型的数据,或针对特殊情况的某些优化),
即使在这些特殊情况下,通常,数据本身的实际表行(包含指向Blob实际数据的指针,或类似的东西),它必须存储在单个IO页上...

例外。唯一可以确定的位置Select *是在子查询中,位于pre ExistsNot Existspredicate子句之后,例如:

   Select colA, colB
   From table1 t1
   Where Exists (Select * From Table2
                 Where column = t1.colA)

编辑:要解决@Mike Sherer的评论,是的,从技术上来说,这是对的,对于您的特殊情况,在美学上也有一点定义。首先,即使请求的列集是存储在某个索引中的列的子集,出于相同的原因,查询处理器也必须获取存储在该索引中的每一列,而不仅仅是获取请求的列-所有I / O必须在页,索引数据就像表数据一样存储在IO页中。因此,如果您为索引页定义“元组”作为存储在索引中的一组列,则该语句仍然为true。
该语句在美学上是正确的,因为重点是它基于I / O页中存储的内容而不是您要的内容来获取数据,并且无论您是访问基表I / O页还是索引均是如此I / O页面。

由于不使用其他原因Select *,请参阅 为什么被SELECT *认为有害?


您确定“总是拉一个元组”吗?嗯,好的,我是对的。如果这样的话,select *内存开销将小于select columnI / O开销。所以如果我们留下网络开销。select *如果费用低于select column
Neel Basu,2010年

10
这不是真的。我想到的一个例子是,当您只需要MySQL中的索引列的值时(例如,仅用于检查行是否存在),而您正在使用MyISAM存储引擎时,它将从数据库中获取数据。 MYI文件,该文件可能在内存中,甚至没有进入磁盘!
Mike Sherov

是的,如果请求的元组集在内存中,将没有I / O,但是那是特殊情况。那么什么是夏日。如果我选择一些索引列,那么整个元组都不会被读取?否则将读取整个元组?
Neel Basu 2010年

我不确定MySQL的缓存方式,但是在SQL Server和Oracle中,即使数据在内存缓存中,它仍然使用与从磁盘访问数据时相同的Page structre访问它。这意味着每页数据需要一个内存I / O ...与从磁盘上完全相同。(当然,内存I / O比磁盘I / O快得多)。确实,这是缓存设计的目标,以使访问过程完全独立于数据的位置。
Charles Bretana

2
您能否进一步说明“出于许多其他原因”?因为这些对我来说还不清楚。如果性能无关紧要,为什么还要关心请求列名呢?
丹尼斯,

111

有几个原因不应该(永远不要)SELECT *在生产代码中使用:

  • 由于您没有向数据库提供有关所需内容的任何提示,因此它首先需要检查表的定义以确定该表上的列。该查询将花费一些时间-在单个查询中花费不多-但随着时间的推移会累加

  • 如果您只需要2/3的列,则选择的数据太多了1/3,这些数据需要从磁盘检索并通过网络发送

  • 如果您开始依赖数据的某些方面(例如,返回的列的顺序),则在重组表并添加新列(或删除现有列)后,您可能会感到讨厌

  • 在SQL Server(不确定其他数据库)中,如果您需要列的子集,则总是有可能非聚集索引可能覆盖该请求(包含所有需要的列)。使用SELECT *,您从一开始就放弃了这种可能性。在这种特殊情况下,将从索引页中检索数据(如果它们包含所有必要的列),因此与进行查询相比,磁盘I / O 内存开销将少得多SELECT *....

是的,最初需要花费更多的时间输入(SQL Prompt for SQL Server之类的工具甚至可以为您提供帮助)-但这确实是一种有规则的情况,无一例外:永远不要在生产代码中使用SELECT *。永远


13
在实践中与您达成一致的同时,从表中获取列数据时,在任何情况下您都是正确的,因为此问题解决了这一点),尽管您对EVER的重视使我指出该规则并非对所有Sql查询都是通用的。特别是,它在EXISTS谓词之后的子查询中使用,(如中Where Exists (Select * From ...)的使用Select *肯定没有问题,并且在某些圈子中被认为是最佳实践。
Charles Bretana

3
@Charles Bretana:是的,这IF EXISTS(SELECT *...是一个特例-既然那里没有数据真正被检索到,但这只是对存在性的检查,SELECT *在那里不是问题……
marc_s 2010年

1
如果我正在开发一个API,可以从我的一张表中检索数据怎么办?由于我不知道用户感兴趣的数据,因此我认为SELECT *是否可以接受?
西蒙·本格森

1
@SimonBengtsson:我仍然会反对这一点-假设您不想在表中的特定列中显示某些“管理”数据,而您不想向客户公开这些数据?我总是会明确指定要获取的列的列表
marc_s 2014年

1
确实如此。查询专门为与API一起使用而设置的视图时该怎么办?
西蒙·本格森

21

您应该始终仅保留select实际需要的列。选择更少而不是更多的效率永远不会降低效率,而且您还会遇到更少的意外副作用-例如按索引访问客户端的结果列,然后通过向表中添加新列来使那些索引变得不正确。

[edit]:意味着访问。愚蠢的大脑仍在醒来。


3
我认为乍看之下不会想到的边缘情况是+1-客户端的索引和添加/更改的列。
Tomas Aschan

1
是的,但是对于常见的列使用数字索引是吗?如果使用ORM,我总是使用字符串键或属性名称访问列数据。
冒犯君主

11
很久以前就看到了这一点,初级程序员从一个表中选择了*并假设了列的顺序;别人换了桌子后,他所有的代码都坏了。我们有什么乐趣。
Paul McKenzie 2010年

7
通常仅出于代码可读性的目的而使用列顺序可能是一个坏主意,SELECT *与它一起使用时则非常糟糕。
莱斯特·马耶斯特(Lèsemajesté),2010年

2
哇,通过指数客户端代码访问列似乎是一个惊人坏主意。因此,依靠列以任何方式出现在结果集中的顺序对我来说是非常肮脏的。
马特·彼得森

7

除非存储大的Blob,否则性能不是问题。不使用SELECT *的最大原因是,如果您将返回的行用作元组,则列会以架构碰巧指定的任何顺序返回,并且如果更改,则必须修复所有代码。

另一方面,如果您使用字典式访问,则列返回的顺序无关紧要,因为您总是按名称访问它们。


6

这立刻使我想起了我正在使用的一个表格,其中包含一个类型的列blob; 它通常包含JPEG图像,Mb大小为几s。

不用说SELECT,除非我真的需要,否则我不会写该专栏。使数据浮动(特别是当我选择了多行时)很麻烦。

但是,我承认我通常会查询表中的所有列。


20
LOB列始终是我最喜欢的SELECT *危险示例。因此,我要为你投票,直到我阅读了第三段。sk,t。如果其他开发人员将BLOB添加到当前没有此类列的表中,会发生什么情况?
APC 2010年

1
@APC,希望我能更多地支持您的评论。想想您可怜的同事,他只想添加一列而不会导致性能严重下降!想想一下,当他们几个小时后发现您无辜的选择*时,他们会多么生气。
Mike Sherov 2010年

1
@ user256007,是的,即使没有BLOB ... BLOB也仅说明了极端示例。检查我对Charles的响应,有时候选择特定的列可以使您从内存中获取数据而无需磁盘!
Mike Sherov

1
@Richard,我认为它们非常适合优化数据库性能,而不是您主要关注的问题,这是99%的时间。与大多数框架一样,它们倾向于对事物进行概括,以实现更快的开发,同时牺牲纯性能。正如Knuth所说:“过早的优化是万恶之源。” 当您需要担心选择列与选择*的性能时(向Twitter询问RoR)时,您可以担心它,然后对其进行优化。如果该框架不足以支持该框架,那么我会说您使用了错误的框架。
Mike Sherov

1
@ user256007 -总的原则是“不使用SELECT *”从marc_s答案已全部reasosn为什么是这样的情况。
APC

6

在执行SQL选择期间,数据库总是会引用表的元数据,而不管对SELECT a,b,c来说是否为SELECT *。为什么?因为那是有关系统表的结构和布局的信息所在的位置。

它必须阅读此信息有两个原因。一,简单地编译语句。它需要确保至少指定一个现有表。此外,自上次执行语句以来,数据库结构可能已更改。

现在,很明显,数据库元数据已缓存在系统中,但仍需要处理。

接下来,使用元数据生成查询计划。每次编译语句时也会发生这种情况。同样,这是针对缓存的元数据运行的,但始终可以完成。

唯一不执行此处理的时间是在DB使用预编译查询或已缓存先前查询时。这是使用绑定参数而不是文字SQL的参数。“ SELECT * FROM TABLE WHERE键= 1”与“ SELECT * FROM TABLE WHERE键=?”不同。并且呼叫上绑定了“ 1”。

数据库在很大程度上依赖页面缓存。许多现代数据库的大小足以完全容纳在内存中(或者,也许我应该说,现代内存足以容纳许多数据库)。然后,后端的主要I / O成本是日志记录和页面刷新。

但是,如果您仍在为数据库打磁盘,则许多系统所做的主要优化是依靠索引中的数据,而不是表本身。

如果你有:

CREATE TABLE customer (
    id INTEGER NOT NULL PRIMARY KEY,
    name VARCHAR(150) NOT NULL,
    city VARCHAR(30),
    state VARCHAR(30),
    zip VARCHAR(10));

CREATE INDEX k1_customer ON customer(id, name);

然后,如果执行“ SELECT ID,从FROM客户WHERE ID = 1命名”,则DB很可能会从索引而不是从表中提取此数据。

为什么?无论如何,它都可能会使用索引来满足查询(相对于表扫描),即使在where子句中未使用“名称”,该索引仍将是查询的最佳选择。

现在,数据库具有满足查询所需的所有数据,因此没有理由直接访问表页面。使用索引可以减少磁盘流量,因为索引中的行密度通常比表中的行高。

这是一些数据库使用的特定优化技术的波浪形解释。许多人有几种优化和调整技术。

最后,SELECT *对于必须手动键入的动态查询很有用,我永远不会将其用于“真实代码”。识别各个列为DB提供了更多信息,可用于优化查询,并为代码提供了更好的控制,以防止架构更改等。


我会否决了您的答案,只是因为您将NOT NULL与PRIMARY KEY一起使用。您有充分的理由这样写吗?
Learner

4

我认为您的问题没有确切答案,因为您正在考虑性能和维护应用程序的便利性。Select column的性能更高select *,但是如果您正在开发面向对象的系统,那么您会喜欢使用,object.properties并且您可能需要在应用的任何部分中使用属性,然后您将需要编写更多方法来在特殊情况下获取属性(如果您不这样做)使用select *并填充所有属性。您的应用需要使用select *并具有良好的性能,在某些情况下,您需要使用选择列来提高性能。然后,您将拥有两个世界中的佼佼者,可以在需要性能时编写和维护应用程序以及性能。


4

这里接受的答案是错误的。当关闭另一个问题作为重复项时,我遇到了这个问题(虽然我仍在写答案-grr-因此下面的SQL引用了另一个问题)。

您应该始终使用SELECT属性,attribute .... NOT SELECT *

它主要用于性能问题。

从用户那里选择名称,其中name ='John';

这不是一个非常有用的例子。考虑改为:

SELECT telephone FROM users WHERE name='John';

如果在(姓名,电话)上有索引,则可以解决查询而不必从表中查找相关值-存在覆盖索引。

此外,假设该表具有一个包含用户图片的BLOB,一个上载的简历以及一个电子表格...使用SELECT *将所有这些信息拉回到DBMS缓冲区中(从缓存中排除其他有用的信息)。然后,所有这些都将使用网络上的正常运行时间以及客户端上用于存储冗余数据的内存发送给客户端。

如果客户端以枚举数组的形式检索数据(例如PHP的mysql_fetch_array($ x,MYSQL_NUM)),也会导致功能问题。也许当代码写为“电话”时,这是SELECT *返回的第三列,但随后有人出现,决定将一个电子邮件地址添加到表中,该地址位于“电话”之前。现在,所需字段移至第四列。


2

无论哪种方式都有做事的理由。我在PostgreSQL上经常使用SELECT *,因为在PostgreSQL中可以使用SELECT *做很多事情,而对于显式列列表则不能做,尤其是在存储过程中。类似地,在Informix中,在继承的表树上执行SELECT *会给您带来锯齿状的行,而显式的列列表却不能,因为还会返回子表中的其他列。

我在PostgreSQL中执行此操作的主要原因是,它确保我得到了特定于表的格式正确的类型。这使我可以获取结果并将其用作PostgreSQL中的表类型。与刚性列列表相比,这还允许在查询中提供更多选项。

另一方面,僵化的列列表为您提供了应用程序级的检查,以确保数据库架构没有以某些方式更改,这可能会有所帮助。(我在另一个级别上进行了此类检查。)

至于性能,我倾向于使用VIEWs和存储过程返回类型(然后是存储过程中的列列表)。这使我可以控制返回什么类型。

但是请记住,我通常对抽象层而不是基表使用SELECT *。


2

从本文引用:

如果没有SELECT *: 那时使用“ SELECT *”时,您是从数据库中选择更多列,并且此列中的某些列可能未被您的应用程序使用。这将在数​​据库系统上产生额外的成本和负载,并通过网络传输更多的数据。

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


0

只是为了在这里没有看到的讨论中增加一些细微差别:就I / O而言,如果您使用的是面向列存储的数据库,则仅查询某些内容就可以减少很多I / O。列。随着我们转向固态硬盘,与面向行的存储相比,它的好处可能会更小一些,但是有a)只读取包含您关心的列的块b)压缩,这通常会大大减少磁盘上数据的大小,因此从磁盘读取的数据量。

如果您不熟悉面向列的存储,那么Postgres的一种实现来自Citus Data,另一种是Greenplum,另一种是Paraccel,另一种(松散地说)是Amazon Redshift。对于MySQL,有Infobright,即已不复存在的InfiniDB。其他商业产品包括HP的Vertica,Sybase IQ,Teradata ...


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.