【问题标题】:select * vs select column选择 * 与选择列
【发布时间】:2011-03-11 22:58:40
【问题描述】:

如果我只需要 2/3 列并且我查询 SELECT * 而不是在选择查询中提供这些列,是否存在与更多/更少 I/O 或内存有关的性能下降?

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

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

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

同时,如果从元组中提取出请求的列,则可能会消耗内存。

所以如果是这样的话,select someColumn 会比 select * 有更多的内存开销

【问题讨论】:

  • 是否有您要询问的特定 RDBMS? SELECT 查询的执行/处理方式可能因数据库而异。
  • 顺便说一句,在 PostgreSQL 中,如果您说 CREATE VIEW foo_view AS SELECT * FROM foo;,然后稍后将列添加到表 foo,这些列将不会按预期自动显示在 foo_view 中。换句话说,此上下文中的* 仅扩展一次(在视图创建时),而不是每个 SELECT。由于 ALTER TABLE 引起的并发症,我想说(在实践中)* 被认为是有害的。
  • @JoeyAdams - 不仅仅是 PostgresQL,这也是 Oracle 的行为。
  • @OMG Ponies:我不知道有类似的帖子。然而,这些并不相似。 @Lèse majesté:我说的是通用 RDBMS。不是关于任何特定的供应商@Joey Adams:嗯,我知道 * 是不安全的。只是想讨论有关的性能问题。

标签: sql performance


【解决方案1】:

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

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

由于这种潜在的结构约束,结果是数据库中的每一行数据必须始终位于一页且仅一页上。它不能跨越数据的多个页面(除了像 blob 这样的特殊事物,其中实际的 blob 数据存储在单独的页面块中,然后实际的表行列只获得一个指针......)。但是这些例外只是,例外,一般不适用,除非在特殊情况下(针对特殊类型的数据,或者针对特殊情况的某些优化)
即使在这些特殊情况下,通常情况下,实际表行数据本身(其中包含指向 Blob 的实际数据的指针,或其他),它必须存储在单个 IO Page 上...

例外。 Select * 唯一可以使用的地方,是在 ExistsNot Exists 谓词子句之后的子查询中,如:

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

编辑:为了解决@Mike Sherer 的评论,是的,从技术上讲,对您的特殊情况有一些定义,从美学上讲,这是真的。首先,即使请求的列集是存储在某个索引中的列的子集,查询处理器也必须获取存储在该索引中的 每个 列,而不仅仅是请求的列,原因相同 - ALL I/O 必须分页进行,索引数据和表数据一样存储在 IO Pages 中。因此,如果将索引页的“元组”定义为存储在索引中的列集,则该语句仍然成立。
并且该声明在美学上是正确的,因为关键是它根据 I/O 页面中存储的内容获取数据,而不是根据您的要求获取数据,无论您访问的是基表 I/O 页面还是索引,这都是正确的I/O 页面。

其他不使用Select *的原因见Why is SELECT * considered harmful?

【讨论】:

  • “它总是拉一个元组”你确定吗?嗯,好吧,所以我是对的。如果是这种情况,select * 的内存开销将低于select column,但 I/O 开销相同。所以如果我们留下网络开销。 select * 如果开销低于 select column
  • 这不是真的。我想到的一个例子是,当您只需要 MySQL 中索引列的值(例如,仅检查行是否存在),并且您正在使用 MyISAM 存储引擎时,它会从MYI 文件,它可能在内存中,甚至不去磁盘!
  • 是的,如果请求的元组集在内存中,则不会有 I/O,但这是特殊情况。那么什么是夏天。如果我选择一些索引列,那么整个元组都不会被读取?否则会读取整个元组?
  • 我不确定 MySql 是如何进行缓存的,但是在 SQL Server 和 Oracle 中,即使数据在内存缓存中,它仍然使用与它相同的 Page structre 访问它从磁盘访问它时。这意味着每页数据需要一个内存 I/O ......与磁盘完全相同。 (当然,除了内存 I/O 比磁盘 I/O 快得多)。事实上,这是缓存设计的目标,使访问过程完全独立于数据的位置。
  • 您能详细说明“出于许多其他原因”吗?因为这些我都不清楚。如果性能无关紧要,为什么还要关心请求列名?
【解决方案2】:
select * from table1 INTERSECT  select * from table2

等于

select distinct t1 from table1 where Exists (select t2 from table2 where table1.t1 = t2 )

【讨论】:

  • 您能否通过突出显示代码并按 Ctrl+K 来格式化您的代码
【解决方案3】:

只是为了在讨论中添加一个我在这里看不到的细微差别:就 I/O 而言,如果您使用带有 column-oriented storage 的数据库,如果您只查询,则可以少做很多 I/O对于某些列。随着我们转向 SSD,与面向行的存储相比,优势可能会小一些,但是 a) 只读取包含您关心的列的块 b) 压缩,这通常会大大减少磁盘上数据的大小,因此从磁盘读取的数据量。

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

【讨论】:

    【解决方案4】:

    Reference taken from this article:

    不带 SELECT *: 当您使用“SELECT *”时,您正在从数据库中选择更多列,并且您的应用程序可能不会使用该列中的某些列。 这将增加数据库系统的额外成本和负载,并通过网络传输更多数据。

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

    【讨论】:

      【解决方案5】:

      做任何事情都是有原因的。我在 PostgreSQL 上使用 SELECT * 很多,因为在 PostgreSQL 中你可以用 SELECT * 做很多事情,而你不能用显式的列列表来做,特别是在存储过程中。同样,在 Informix 中,继承表树上的 SELECT * 可以为您提供参差不齐的行,而显式列列表则不能,因为还会返回子表中的其他列。

      我在 PostgreSQL 中这样做的主要原因是它确保我得到一个特定于表的格式良好的类型。这允许我获取结果并将它们用作 PostgreSQL 中的表类型。与严格的列列表相比,这也允许在查询中提供更多选项。

      另一方面,严格的列列表可以让您在应用程序级别检查数据库模式没有以某些方式发生变化,这可能会有所帮助。 (我在另一个层面做这样的检查。)

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

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

      【讨论】:

        【解决方案6】:

        这里接受的答案是错误的。当another question 被关闭时,我遇到了这个问题(当我还在写我的答案时——grr——因此下面的 SQL 引用了另一个问题)。

        你应该总是使用 SELECT 属性,属性.... NOT SELECT *

        主要是针对性能问题。

        SELECT name FROM users WHERE name='John';

        不是一个很有用的例子。请考虑:

        SELECT telephone FROM users WHERE name='John';
        

        如果(姓名、电话)上有索引,则无需从表中查找相关值即可解析查询 - 有一个覆盖索引。

        进一步,假设该表有一个包含用户照片的 BLOB、上传的简历和电子表格... 使用 SELECT * 会将所有这些信息拉回 DBMS 缓冲区(从缓存中强制删除其他有用的信息)。然后将其全部发送到客户端,使用网络上的正常运行时间和客户端上的内存以获取冗余数据。

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

        【讨论】:

          【解决方案7】:

          除非您要存储大型 blob,否则性能不是问题。不使用 SELECT * 的主要原因是,如果您将返回的行用作元组,则列会以架构指定的任何顺序返回,如果发生更改,您将不得不修复所有代码。

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

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              【解决方案9】:

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

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

              • 如果您只需要 2/3 的列,那么您选择的数据过多,需要从磁盘检索并通过网络发送

              • 如果您开始依赖数据的某些方面,例如返回的列的顺序,一旦重新组织表并添加新列(或删除现有列),您可能会得到一个令人讨厌的惊喜

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

              是的,最初需要更多的输入(像SQL Prompt 这样的 SQL Server 工具甚至会帮助你)——但这确实是一个没有任何例外的规则:永远不要在你的生产代码。 永远。

              【讨论】:

              • 虽然在实践中同意你的观点,但在从表中获取列数据时,你在所有情况下都是正确的,正如这个问题所解决的那样),你对 EVER 的强调仍然促使我指出这条规则是不适用于所有 Sql 查询...具体来说,它在 EXISTS 谓词之后的子查询中使用,(如在Where Exists (Select * From ... 中)Select * 的使用当然没有问题,并且在某些圈子中被认为是最佳实践。
              • @Charles Bretana:是的,IF EXISTS(SELECT *... 是一个特例——因为在那里,没有真正检索到数据,但它只是检查存在,SELECT * 不是问题。 .
              • 如果我正在开发一种 API 可以从我的一个表中检索数据,那该怎么办。由于我不知道用户对哪些数据感兴趣,我想 SELECT * 可以接受吗?
              • @SimonBengtsson:我仍然反对这一点——假设您在表中的特定列中有一些“管理”数据,您不想向客户公开这些数据?我会总是明确指定要获取的列列表
              • 确实如此。查询专门设置为与 API 一起使用的视图时会怎样?
              【解决方案10】:

              在 SQL 选择期间,数据库总是会引用表的元数据,不管它是否是 SELECT * for SELECT a, b, c... 为什么?因为那是系统中表格结构和布局的信息所在。

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

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

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

              只有当数据库正在使用预编译查询或缓存了以前的查询时,才会执行此处理。这是使用绑定参数而不是文字 SQL 的论据。 “SELECT * FROM TABLE WHERE key = 1”是与“SELECT * FROM TABLE WHERE key = ?”不同的查询。并且“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, name FROM customer WHERE id = 1”,您的数据库很可能会从索引中提取此数据,而不是从表中提取。

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

              现在数据库拥有满足查询所需的所有数据,因此没有理由自己访问表页面。使用索引可以减少磁盘流量,因为与一般表相比,索引中的行密度更高。

              这是对某些数据库使用的特定优化技术的粗略解释。许多有多种优化和调整技术。

              最后,SELECT * 对于您必须手动输入的动态查询很有用,我永远不会将它用于“真实代码”。单个列的标识为数据库提供了更多可用于优化查询的信息,并让您更好地控制代码以防止架构更改等。

              【讨论】:

              • 威尔,我对你的答案投了反对票,只是因为你使用 NOT NULL 和 PRIMARY KEY。你有充分的理由这样写吗?
              【解决方案11】:

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

              [编辑]:意味着访问。愚蠢的大脑还在苏醒。

              【讨论】:

              • +1 表示我相信乍一看不会有多少人想到的边缘情况 - 客户端的索引和添加/更改的列。
              • 是的,但是对列使用数字索引很常见吗?如果使用 ORM,我总是使用字符串键或属性名称访问列数据。
              • 很久以前看到这个,初级程序员从表中选择*并假设列顺序;其他人更改表后,他的所有代码都崩溃了。我们玩得很开心。
              • 仅仅为了代码的可读性而使用列顺序可能是个坏主意,使用SELECT * 就更糟糕了。
              • 哇,在客户端代码中按索引访问列似乎是一个现象上的坏主意。就此而言,依赖列在结果集中出现的顺序以任何方式对我来说感觉很脏。
              【解决方案12】:

              这立即让我想起了我正在使用的包含blob 类型列的表;它通常包含一个 JPEG 图像,大小为几个 Mbs。

              不用说我没有SELECT 那个专栏,除非我真的需要它。让这些数据四处飘荡——尤其是当我选择多行时——只是一件麻烦事。

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

              【讨论】:

              • LOB 列始终是我最喜欢的 SELECT * 危险示例。所以我正要给你投票,直到我读到第三段。啧啧啧。如果其他开发人员将 BLOB 添加到当前没有此类列的表中会发生什么?
              • @APC,我希望我能更多地支持你的评论。想想你可怜的同事,他只想添加一列而不导致巨大的性能崩溃!想想当他们在几个小时后发现你看起来很无辜的选择时会多么生气。
              • @user256007,是的,即使没有 BLOB ...... BLOB 只是说明了一个极端的例子。查看我对 Charles 的回复,有时选择特定的列可以让您从内存中获取数据,甚至无需进入磁盘!
              • @Richard,我认为它们非常适合优化数据库性能不是您主要关注的问题,这是 99% 的时间。与大多数框架一样,它们倾向于概括事物以实现更快的开发,同时牺牲纯粹的性能。正如 Knuth 所说:“过早的优化是万恶之源。”当您需要担心 select 列与 select * 的性能时,(向 Twitter 询问 RoR)您可以担心并优化它。如果框架不够强大,无法支持这一点,那么我会说你使用了错误的框架。
              • @user256007 - 一般规则是“不要使用 SELECT *”。marc_s 的回答说明了为什么会出现这种情况。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-06-27
              • 2014-01-04
              • 2018-03-17
              • 2011-04-08
              • 1970-01-01
              • 1970-01-01
              • 2012-07-11
              相关资源
              最近更新 更多