为什么认为SELECT *有害?

为什么SELECT *是不好的做法?如果您添加了想要的新列,这难道不意味着需要更改的代码更少吗?

我知道SELECT COUNT(*)在一些db上是一个性能问题,但如果你真的想要每一列呢?

101190 次浏览

即使您现在希望选择每一列,也可能不希望在某人添加一个或多个新列之后选择每一列。如果你用SELECT *编写查询,你就会冒着这样的风险:在某些时候,某人可能会添加一列文本,这使得你的查询运行得更慢,即使你实际上并不需要那一列。

如果您添加了想要的新列,这难道不意味着需要更改的代码更少吗?

如果您确实想要使用新列,那么无论如何您都必须对代码进行大量其他更改。你只保存了, new_column -仅仅几个字符的输入。

通常,你必须将SELECT * ...的结果适合各种类型的数据结构。如果不指定结果到达的顺序,正确排列所有内容可能会很棘手(而且更模糊的字段更容易错过)。

通过这种方式,您可以出于各种原因向表中添加字段(甚至在表中间),而不会破坏整个应用程序中的sql访问代码。

当你只需要几列时使用SELECT *意味着传输的数据比你需要的多得多。这增加了数据库上的处理,并增加了将数据获取到客户端的延迟。此外,它在加载时将使用更多内存,在某些情况下会使用更多内存,例如大型BLOB文件,这主要是关于效率。

然而,除此之外,在查看查询时更容易看到正在加载哪些列,而不必查找表中的内容。

是的,如果您确实添加了一个额外的列,它会更快,但在大多数情况下,您希望/需要使用查询更改代码以接受新列,并且有可能获得您不想要/期望的列会导致问题。例如,如果获取所有列,然后依赖循环中的顺序来分配变量,然后再添加一个,或者如果列的顺序发生了变化(从备份恢复时就发生过这种情况),它可能会丢弃所有内容。

这也是同样的原因,如果你在做INSERT,你应该总是指定列。

在很多情况下,SELECT *会在应用程序的运行时导致错误,而不是在设计时。它隐藏了应用程序中列更改或坏引用的信息。

如果您真的想要每个列,我没有看到select(*)和命名列之间的性能差异。命名列的驱动程序可能只是为了明确您希望在代码中看到哪些列。

但是,通常情况下,您不希望每个列和select(*)会导致数据库服务器做不必要的工作,并且必须通过网络传递不必要的信息。它不太可能造成明显的问题,除非系统被大量使用或网络连接很慢。

我不认为这真的可以有一个通用的规则。在许多情况下,我避免使用SELECT *,但我也使用过SELECT *非常有用的数据框架。

和所有事情一样,有收益也有成本。我认为收益与成本的等式的一部分是你对数据结构有多少控制。在SELECT *工作良好的情况下,数据结构受到严格控制(它是零售软件),因此没有太大的风险,有人会将一个巨大的BLOB字段发送到表中。

有三个主要原因:

  • 将数据传输给消费者的效率低下。当您使用SELECT *时,您从数据库中检索的列通常比应用程序实际运行所需的列要多。这将导致更多的数据从数据库服务器转移到客户端,从而降低访问速度并增加机器上的负载,并在网络上花费更多时间。当有人将新列添加到底层表中,而这些列在原始消费者编码其数据访问时并不存在,也不需要时,情况尤其如此。

  • < >强索引的问题。考虑这样一个场景,您想调优一个查询以达到较高的性能水平。如果您要使用*,并且它返回的列比实际需要的多,那么服务器通常必须执行更昂贵的方法来检索您的数据。例如,您不能创建一个仅仅覆盖SELECT列表中的列的索引,而且即使您这样做了(包括所有列[shudder]),下一个向底层表添加列的人会导致优化器忽略您优化的覆盖索引,您可能会发现查询的性能会毫无明显原因地大幅下降。

  • < >强约束力的问题。当您选择*时,可以从两个不同的表中检索相同名称的两列。这通常会导致数据消费者崩溃。想象一个连接两个表的查询,这两个表都包含一个名为“ID”的列。消费者怎么知道哪个是哪个?当底层表结构发生变化时,SELECT *也会混淆视图(至少在某些版本的SQL Server中)——视图没有重新构建,返回的数据可能是无意义的。最糟糕的是,您可以小心地为您的列命名任何您想要的名称,但下一个人可能无法知道他必须担心添加的列将与您已经开发的名称发生冲突。

但这对SELECT *来说也不全是坏事。我在以下用例中大量使用它:

  • < >强特别查询。当试图调试一些东西时,特别是在一个我可能不熟悉的窄表上,SELECT *通常是我最好的朋友。它帮助我看到发生了什么,而不需要做大量的研究,以了解底层的列名是什么。列名越长,这个“加号”就越大。

  • 在以下用例中,SELECT *很好,关于它是性能杀手的谣言只是都市传说,多年前可能有一些道理,但现在不是了:

    SELECT COUNT(*) FROM table;
    

    在本例中,*表示“计算行数”。如果你要使用列名而不是*,它将计算该列的值不为空的行数。COUNT(*),对我来说,真正的概念是你正在计数,并且你避免了由null从你的聚合中被消除而引起的奇怪的边情况。

    这类查询也是一样:

    SELECT a.ID FROM TableA a
    WHERE EXISTS (
    SELECT *
    FROM TableB b
    WHERE b.ID = a.B_ID);
    

    在任何有价值的数据库中,*只表示“一行”。在子查询中输入什么并不重要。有些人在SELECT列表中使用b的ID,或者他们会使用数字1,但在我看来,这些约定几乎是毫无意义的。你的意思是“计算行数”,这就是*的含义。大多数查询优化器都足够聪明,知道这一点。(虽然说实话,我只有知道这是真实的SQL Server和Oracle。)

如果你向表中添加字段,它们将自动包含在你使用select *的所有查询中。这看起来很方便,但它会使您的应用程序变慢,因为您获取的数据比您需要的要多,并且它实际上会在某些时候使您的应用程序崩溃。

在结果的每一行中可以获取多少数据是有限制的。如果向表中添加字段导致结果超过了限制,则在尝试运行查询时将收到错误消息。

这是一种很难发现的错误。您在一个地方做了更改,而在另一个实际上根本不使用新数据的地方它就会崩溃。它甚至可能是一个不太常用的查询,以至于在有人使用它之前需要一段时间,这使得将错误与更改联系起来更加困难。

如果指定希望在结果中显示哪些字段,就不会出现这种开销溢出。

SELECT语句中的星号“*”是查询中涉及的表中所有列的简写。

性能

*缩写可能更慢,因为:

  • 并不是所有的字段都被索引,这迫使对整个表进行扫描——效率较低
  • 你保存的通过有线发送SELECT *的内容有全表扫描的风险
  • 返回比需要的更多的数据
  • 使用可变长度数据类型返回尾随列会导致搜索开销

维护

当使用SELECT *时:

  • 不熟悉代码库的人在能够进行适当的更改之前,将被迫查阅文档以了解返回的是哪些列。让代码更具可读性,减少不熟悉代码的人所需要的模糊性和工作,从长远来看可以节省更多的时间和精力。
  • 如果代码依赖于列顺序,SELECT *将隐藏一个等待发生的错误,如果一个表的列顺序发生了改变。
  • 即使在编写查询时需要每一列,将来也可能不是这样
  • 这种用法使分析复杂化

设计

SELECT *是一个反模式< em > < / em >:

  • 查询的目的不那么明显;应用程序使用的列是不透明的
  • 它打破了尽可能使用严格类型的模块化规则。Explicit几乎在所有情况下都更好。

什么时候应该使用“SELECT *”?

当显式需要涉及表中的每一列时,而不是编写查询时存在的每一列时,使用SELECT *是可以接受的。数据库将在内部将*展开为完整的列列表—没有性能差异。

否则,显式地列出要在查询中使用的每一列—最好是在使用表别名时。

如果您在SELECT语句中为列命名,则它们将按照指定的顺序返回,因此可以安全地由数值索引引用。如果使用“SELECT *”,可能会以任意顺序接收列,因此只能安全地按名称使用列。除非您事先知道要对添加到数据库中的任何新列做什么,否则最可能的正确操作是忽略它。如果要忽略添加到数据库中的任何新列,那么检索它们没有任何好处。

可以把它看作是减少应用程序和数据库之间的耦合。

为了总结“代码气味”方面:
SELECT *在应用程序和模式之间创建了一个动态依赖关系。限制它的使用是使依赖更加明确的一种方法,否则对数据库的更改更有可能使应用程序崩溃。< / p >

在设计方案之前理解您的需求(如果可能的话)。

了解数据, 1)索引 2)所使用的存储类型; 3)供应商引擎或功能;即……缓存,内存功能 4)数据类型 5)桌子大小 6)查询频率 7)如果资源是共享的,相关的工作量 8)测试< / p >

A)要求会有所不同。如果硬件不能支持预期的工作负载,则应该重新评估如何在工作负载中提供需求。关于向表中添加的列。如果数据库支持视图,您可以使用特定的命名列创建特定数据的索引(?)视图(vs.选择'*')。定期检查您的数据和模式,以确保您永远不会遇到“输入垃圾”->“输出垃圾”综合征。

假设没有其他解;你可以考虑以下几点。一个问题总是有多种解决方案。

1)索引:select *将执行一个表罐。根据各种因素,这可能涉及到磁盘寻道和/或与其他查询的争用。如果表是多用途的,请确保所有查询都是高性能的,并在您的目标时间以下执行。如果有大量数据,而您的网络或其他资源没有调优;你需要考虑到这一点。数据库是一个共享环境。

2)存储类型。Ie:如果你使用SSD,磁盘或内存。I/O时间和系统/cpu上的负载会有所不同。

3) DBA是否可以调优数据库/表以获得更高的性能?假设出于某种原因,团队已经决定选择“*”是问题的最佳解决方案;可以将DB或表加载到内存中。(或者其他方法…也许反应被设计成有2-3秒的延迟?——而广告的作用是为公司赚取收入……)

4)从基线开始。了解您的数据类型,以及如何显示结果。更小的数据类型、字段数量会减少结果集中返回的数据量。这将为其他系统需求留下可用资源。系统资源通常是有限制的;“总是”工作低于这些限制,以确保稳定性和可预测的行为。

5)表/数据的大小。选择“*”在小表中很常见。它们通常适合内存,并且响应时间很快。再次……回顾您的需求。特征蠕变计划;总是为当前和未来可能的需求做计划。

6)查询/查询频率。了解系统上的其他工作负载。如果这个查询每秒发出一次,并且表很小。结果集可以设计为保留在缓存/内存中。然而,如果查询是一个频繁的批处理过程,有千兆字节/兆兆字节的数据……最好分配额外的资源以确保其他工作负载不受影响。

7)相关工作量。了解如何使用资源。网络/系统/数据库/表/应用程序是专用的还是共享的?谁是利益相关者?这是为了生产、开发还是QA?这是暂时的“权宜之计”吗?你测试过这个场景了吗?您会惊讶于当前硬件上存在的问题之多。(是的,性能很快……但设计/性能仍然下降。)系统需要每秒执行10K个查询还是每秒执行5-10个查询?数据库服务器是专用的,还是其他应用程序在共享资源上执行监视。一些应用程序/语言;O/S将消耗100%的内存,导致各种症状/问题。

8)测试:测试你的理论,尽可能多地理解。你选择的“*”问题可能是一个大问题,或者它可能是你甚至不需要担心的事情。

使用列名进行选择提高了数据库引擎从索引访问数据的可能性,而不是查询表数据。

当数据库模式发生变化时,SELECT *使您的系统暴露在意想不到的性能和功能变化中,因为您要将任何新列添加到表中,即使您的代码还没有准备好使用或显示这些新数据。

还有一个更实际的原因:钱。当你使用云数据库时,你必须为数据处理付费,没有任何解释来读取你将立即丢弃的数据。

例如:BigQuery:

查询价格

查询定价是指运行SQL命令和用户定义函数的成本。BigQuery根据一个指标对查询收费:处理的字节数。

控制投影-避免选择*:

最佳实践:控制投影—只查询所需的列。

投影指的是查询读取的列数。投影多余的列会导致额外的(浪费的)I/O和物化(写入结果)。

使用SELECT *是最昂贵的查询数据的方法。当您使用SELECT *时,BigQuery会对表中的每一列进行全面扫描。