为什么SELECT *被认为是有害的?

为什么SELECT *坏习惯? 如果你添加了一个你想要的新列,这不是说更less的代码要改变吗?

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

主要有三个原因:

  • 将数据传送给消费者的效率不高。 当你select*时,你经常从数据库中检索更多的列,而不是你的应用程序真正需要的function。 这会导致更多数据从数据库服务器迁移到客户端,从而降低访问速度并增加您的计算机上的负载,并花费更多时间在networking上传输。 当有人向不存在的基础表添加新列时,尤其是当原始消费者对其数据访问进行编码时不需要这些列。

  • 索引问题。 考虑一个场景,您想要将查询调整到较高的性能水平。 如果您要使用*,并且返回的列数比实际需要的多,则服务器通常必须执行比其他方法更昂贵的方法来检索数据。 例如,你将无法创build一个简单地覆盖了你的SELECT列表中的列的索引,即使你做了(包括所有列[ shudder ]),下一个出现并添加了一列到底层的人表会导致优化器忽略优化的覆盖索引,并且您可能会发现查询的性能会显着下降,因为没有明显的原因。

  • 绑定问题。 当您select*时,可以从两个不同的表中检索两个同名的列。 这可能会经常导致数据使用者崩溃。 设想一个连接两个表的查询,这两个表都包含一个名为“ID”的列。 消费者如何知道哪个是哪个? 当底层表结构发生变化时,SELECT *也会混淆视图(至less在某些版本的SQL Server中) – 视图不会被重build,而返回的数据可能是无稽之谈 。 而最糟糕的是,你可以随心所欲地列出你想要的列名,但是下一个出现的人可能无法知道他不得不担心增加一个与你已经开发的列相冲突的列名。

但是对于SELECT *来说并不全是坏事。 我为这些用例大量使用它:

  • 即席查询。 当试图debugging某些东西时,尤其是在我可能不熟悉的狭义表格中,SELECT *往往是我最好的朋友。 它可以帮助我看看发生了什么事情,而无需进行大量的研究来了解底层的列名。 列名越长,这就越大。

  • 当*表示“连续”时。 在下面的用例中,SELECT *就好了,传言这是一个性能杀手,只不过是城市传说,而这个传奇在很多年前可能已经有了一些有效性,但现在不是:

     SELECT COUNT(*) FROM table; 

    在这种情况下,*表示“统计行数”。 如果您要使用列名称而不是*, 则会计算该列的值不为空的行 。 COUNT(*),对我来说,真正驱动回家的概念,你正在计数 ,你避免了由于您的聚合消除NULL的奇怪的边缘情况。

    这种types的查询也一样:

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

    在任何有价值的数据库中,*只是意味着“连续”。 无论你放在子查询中是什么。 有些人在SELECT列表中使用b的ID,否则他们会使用数字1,但是这些惯例几乎是无意义的。 你的意思是“计数行”,这就是*表示的意思。 大多数查询优化器都有足够的智能来了解这一点。 (尽pipe说实话,我只知道 SQL Server和Oracle是这样的。)

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

性能

*速记可能会变慢,因为:

  • 并非所有的字段都被编入索引,强制全表扫描 – 效率较低
  • 通过电线发送SELECT *可能会导致全表扫描
  • 返回比需要更多的数据
  • 使用可变长度数据types返回尾部列可能导致search开销

保养

当使用SELECT *

  • 不熟悉代码库的人将被迫查阅文档,以便知道哪些列将被退回,然后才能进行有效的更改。 使代码更具可读性,最大限度地减less不熟悉代码的人所需的模糊性和工作量,从长远来看可以节省更多的时间和精力。
  • 如果代码依赖于列顺序,那么如果表的列顺序发生更改,则SELECT *将隐藏等待发生的错误。
  • 即使您在编写查询时需要每一列,未来也可能不是这样
  • 使用会使分析变得复杂

devise

SELECT *是一种反模式

  • 查询的目的不太明显; 应用程序使用的列是不透明的
  • 它打破了严格打字的模块化规则,只要有可能。 显式几乎普遍更好。

何时应该使用“SELECT *”?

当涉及表中的每一列都有明确的需求时,使用SELECT *是可以接受的,而不是写入查询时存在的每一列。 数据库将内部扩展到列的完整列表中 – 没有性能差异。

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

即使您想要现在select每一列,在某人添加一个或多个新列之后,您可能也不想select每一列。 如果使用SELECT *编写查询,则冒着某种程度上可能会添加一列文本的风险,即使您实际上不需要该列,也会使查询运行得更慢。

如果你添加了一个你想要的新列,这不是说更less的代码要改变吗?

有可能是,如果你真的想要使用新的列,那么你将不得不对你的代码进行很多其他的修改。 你只保存, new_column – 只有几个字符的打字。

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

在很多情况下,SELECT *会在运行时在您的应用程序中导致错误,而不是在devise阶段。 它隐藏了列更改的知识或应用程序中的错误引用。

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

通常,您不希望每列和select(*)都会导致数据库服务器不必要的工作,并且不必要的信息必须通过networking传递。 除非系统被大量使用或networking连接速度较慢,否则不太可能引起明显的问题。

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

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

这是很难find的那种错误。 你在一个地方做了一个改变,在其他一些根本没有使用新数据的地方爆炸了。 它甚至可能是一个不太经常使用的查询,所以需要一段时间才能有人使用它,这使得更难以连接到错误的变化。

如果在结果中指定了您想要的字段,则可以避免这种开销溢出。

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

总结一下“代码味道”方面:
SELECT *在应用程序和模式之间创builddynamic依赖关系。 限制它的使用是更加定义依赖关系的一种方法,否则数据库的更改会导致应用程序崩溃。

一般来说,你必须将你的SELECT * ...的结果适合于各种types的数据结构。 在没有指定结果到达的顺序的情况下,将所有内容正确地排成一行可能会非常棘手(并且更难理解的字段更容易错过)。

这样,你可以添加字段到你的表(即使在他们中间)出于各种各样的原因,而不必在整个应用程序中的SQL访问代码。

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

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

是的,如果你添加了一个额外的列,速度会更快,但是在大多数情况下,你需要/需要改变你的代码,使用查询来接受新的列,而且有可能让你不想要的东西,想要/期望会导致问题。 例如,如果抓取所有列,则依靠循环中的顺序来分配variables,然后添加一个variables,或者如果列顺序发生更改(从备份还原时发生这种情况),则可能会抛出所有内容。

这也是同样的推理,为什么如果你正在做一个INSERT你应该总是指定列。

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

和所有的事情一样,也有好处和成本。 我认为,利益与成本等式的一部分就是对数据结构有多less控制。 在SELECT *运行良好的情况下,数据结构受到严格的控制(这是零售软件),所以没有太大的风险,有人将一个巨大的BLOB字段放到表中。

参考这篇文章。

切勿使用“SELECT *”,

我发现只有一个理由使用“select*”

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

在devise模式之前理解你的需求(如果可能的话)。

了解数据,1)索引2)使用的存储types,3)供应商引擎或function; 即…caching,内存中的function4)数据types5)表6的大小)查询的频率7)相关的工作量,如果共享资源8)testing

A)要求会有所不同。 如果硬件无法支持预期的工作负载,则应重新评估如何提供工作负载中的需求。 关于表格的添加列。 如果数据库支持视图,则可以使用特定的命名列(与select“*”)来创build特定数据的索引(?)视图。 定期检查你的数据和模式,以确保你永远不会遇到“垃圾进入” – >“垃圾”综合征。

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

1)索引:select *将执行桌面扫描。 取决于各种因素,这可能涉及磁盘search和/或与其他查询的争用。 如果表是多用途的,确保所有查询都是高性能的,并且在目标时间之下执行。 如果有大量数据,并且您的networking或其他资源未调整, 你需要考虑到这一点。 数据库是一个共享的环境。

2)存储types。 即:如果您使用SSD,磁盘或内存。 I / O次数和系统/ CPU的负载会有所不同。

3)DBA可以调整数据库/表以获得更高的性能吗? 无论出于何种原因,团队决定select“*”是解决问题的最佳scheme。 数据库或表可以加载到内存中。 (或者其他的方法……也许这个回应的目的是为了回应2-3秒的延迟?—广告起到赢得公司收入的作用……)

4)从基线开始。 了解你的数据types,以及如何呈现结果。 较小的数据types,字段数量减less了结果集中返回的数据量。 这使得资源可用于其他系统需求。 系统资源通常是有限制的; “总是”在这些限制之下工作,以确保稳定性和可预测的行为。

5)表/数据的大小。 select'*'是很小的表格。 它们通常适合记忆,响应时间也很快。 再次….审查您的要求。 计划function蠕变; 总是为当前和未来可能的需求做计划。

6)查询/查询的频率。 请注意系统上的其他工作负载。 如果这个查询每秒都会closures,并且表格很小。 结果集可以devise成保留在caching/内存中。 但是,如果查询是一个千兆/千兆字节数据的频繁批处理过程…您可能更愿意投入额外的资源来确保其他工作负载不受影响。

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

8)testing:testing你的理论,并尽可能多地理解。 你select的'*'问题可能是一个大问题,或者它可能是你甚至不需要担心的事情。

使用列名select提高了数据库引擎可以从索引访问数据而不是查询表数据的概率。

当您的数据库架构发生变化时,SELECT *会将您的系统暴露给意外的性能和function更改,因为即使您的代码没有准备好使用或显示新数据,您仍将获得添加到表中的新列。