代码之家  ›  专栏  ›  技术社区  ›  Theodore R. Smith

为什么选择*被认为是有害的?

  •  222
  • Theodore R. Smith  · 技术社区  · 14 年前

    为什么是 SELECT * 不好的练习?如果您添加了一个您想要的新列,这是否意味着更改的代码更少?

    我明白 SELECT COUNT(*) 在某些DBS上是一个性能问题,但是如果您真的想要每一列呢?

    16 回复  |  直到 5 年前
        1
  •  284
  •   Dave Markle    13 年前

    有三个主要原因:

    • 向消费者移动数据的效率低下。 当您选择*时,您通常从数据库中检索的列比应用程序实际需要的要多。这会导致更多的数据从数据库服务器移动到客户机,降低访问速度,增加机器上的负载,以及花费更多的时间在网络上传输。当有人将新列添加到不存在的基础表中,并且在原始使用者对其数据访问进行编码时不需要这些基础表时,情况尤其如此。

    • 索引问题。 考虑一个场景,您希望将查询调到高性能级别。如果要使用*,并且返回的列比实际需要的多,那么服务器通常必须执行比其他方法更昂贵的方法来检索数据。例如,即使创建了索引(包括所有列),也无法创建只覆盖选择列表中列的索引。[ 战栗 ],如果下一个家伙向基础表中添加了一列,则会导致优化器忽略优化的覆盖索引,并且您可能会发现查询的性能会因为没有明显的原因而大幅下降。

    • 绑定问题。 选择*后,可以从两个不同的表中检索两个同名的列。这通常会使数据消费者崩溃。设想一个连接两个表的查询,两个表都包含一个名为“id”的列。消费者如何知道哪个是哪个?当基础表结构更改时,select*还可能混淆视图(至少在某些版本的SQL Server中如此)-- the view is not rebuilt, and the data which comes back can be nonsense . 最糟糕的是,你可以随意给你的列命名,但是下一个来的人可能不知道他必须担心添加一个与你已经开发的名称相冲突的列。

    但对select*来说并不都是坏事。我在这些用例中大量使用它:

    • 临时查询。 当尝试调试某些东西时,特别是在一个我可能不熟悉的窄表上,select*通常是我最好的朋友。它可以帮助我了解正在发生的事情,而不必对底层的列名做大量的研究。这将是一个更大的“加”列名称得到的时间越长。

    • 当*表示“一行”。 在以下用例中,select*很好,有传言说它是一个性能杀手只是城市传说,这些传说可能在很多年前就已经有了一些有效性,但现在还没有:

      SELECT COUNT(*) FROM table;
      

      在这种情况下,*表示“计算行数”。如果要使用列名而不是*, 它将计算该列值不为空的行数 . 对我来说,真的把你计算的概念带回家了。 ,并且可以避免由于从聚合中消除了空值而导致的奇怪的边缘情况。

      这种类型的查询也一样:

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

      在任何值得一试的数据库中,*只意味着“一行”。在子查询中放入什么并不重要。有些人在选择列表中使用b的id,或者他们将使用数字1,但在我看来,这些约定是非常荒谬的。你的意思是“数一行”,这就是*的意思。大多数查询优化器都非常聪明,足以知道这一点。(说实话,我只是 知道 这在SQL Server和Oracle中是正确的。)

        2
  •  84
  •   OMG Ponies    14 年前

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

    性能

    这个 * 速记速度可能较慢,因为:

    • 并非所有字段都被编入索引,从而强制进行全表扫描-效率较低
    • 保存发送的内容 SELECT * 在线路上有全扫描的危险
    • 返回的数据多于所需的数据
    • 使用可变长度数据类型返回尾随列可能会导致搜索开销

    维护

    使用时 选择* :

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

    设计

    选择* 是一个 反模式 :

    • 查询的目的不太明显;应用程序使用的列是不透明的
    • 它打破了尽可能使用严格类型的模块化规则。直白几乎是世界上最好的。

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

    可以使用 选择* 当表中的每一列都有明确的需求时,与编写查询时存在的每一列相反。数据库将在内部将*扩展到完整的列列表中-没有性能差异。

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

        3
  •  18
  •   Mark Byers    14 年前

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

    如果您添加了一个您想要的新列,这是否意味着更改的代码更少?

    如果您真的想使用这个新列,那么很有可能您无论如何都要对代码做很多其他的更改。你只是在存钱 , new_column -只需输入几个字符。

        4
  •  4
  •   supercat    14 年前

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

        5
  •  3
  •   Andrew Lewis    14 年前

    在许多情况下,select*将在应用程序的运行时而不是在设计时导致错误。它隐藏了有关列更改或应用程序中错误引用的知识。

        6
  •  3
  •   brabster    14 年前

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

    但是,通常情况下,您不希望每一列和select(*)都会导致数据库服务器不必要的工作和不必要的信息必须通过网络传递。除非系统使用率高或网络连接速度慢,否则不太可能引起明显的问题。

        7
  •  3
  •   Kelly S. French    14 年前

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

    总结“代码气味”方面:
    SELECT * 在应用程序和架构之间创建动态依赖项。限制它的使用是使依赖关系更加定义的一种方法,否则对数据库的更改更有可能导致应用程序崩溃。

        8
  •  3
  •   Guffa    10 年前

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

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

    这是一种很难发现的错误。你在一个地方做了一个改变,而在另一个根本不使用新数据的地方它就爆炸了。它甚至可能是一个使用频率较低的查询,因此在某人使用它之前需要一段时间,这使得将错误连接到更改更加困难。

    如果指定要在结果中包含哪些字段,则可以避免此类开销溢出。

        9
  •  2
  •   Anvesh    9 年前

    Reference taken from this article.

    永远不要使用“select*”,

    我只找到一个使用“select*”的原因。

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

        10
  •  1
  •   jkerian    14 年前

    一般来说,您必须符合 SELECT * ... 不同类型的数据结构。如果不指定结果到达的顺序,则很难正确地排列所有内容(更模糊的字段更容易丢失)。

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

        11
  •  1
  •   Tarka user370305    14 年前

    使用 SELECT * 当您只需要几列时,意味着传输的数据比您需要的要多得多。这增加了对数据库的处理,并增加了向客户机获取数据的延迟。除此之外,加载时它将使用更多的内存,在某些情况下更为重要,例如大型blob文件,这主要是为了提高效率。

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

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

    这也是同样的道理,如果你在做 INSERT 您应该始终指定列。

        12
  •  1
  •   JMarsch    14 年前

    我不认为这真的会有一个笼统的规则。在许多情况下,我避免使用select*,但我也使用了数据框架,其中select*非常有用。

    就像所有的事情一样,都有好处和成本。我认为收益与成本等式的一部分就是你对数据结构的控制程度。在select*运行良好的情况下,数据结构受到严格控制(它是零售软件),因此没有太大的风险,有人会将一个巨大的blob字段喷嚏到表中。

        13
  •  0
  •   kllee    8 年前

    在设计模式之前了解您的需求(如果可能的话)。

    了解数据, 1)标引 2)使用的存储类型, 3)供应商引擎或功能;即缓存、内存功能 4)数据类型 5)表的大小 6)查询频率 7)资源共享时的相关工作负载 8)测试

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

    假设没有其他解决方案,您可以考虑以下内容。一个问题总是有多种解决方案。

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

    2)储存类型。如果你使用的是固态硬盘、磁盘或内存。I/O时间和系统/CPU上的负载会有所不同。

    3)DBA是否可以调整数据库/表以获得更高的性能?不管出于什么原因,团队已经决定选择“*”是解决问题的最佳方案;数据库或表是否可以加载到内存中。(或者其他方法……也许响应被设计为以2-3秒的延迟响应?——当广告播放以赚取公司收入时……)

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

    5)表/数据的大小。选择“*”对于小表很常见。它们通常适合内存,响应时间很快。再次……检查您的需求。为特性爬行做计划;总是为当前和未来可能的需求做计划。

    6)查询/查询的频率。注意系统上的其他工作负载。如果这个查询每秒钟触发一次,并且表很小。结果集可以设计为保留在缓存/内存中。但是,如果查询是一个经常使用千兆字节/兆字节数据的批处理过程,那么您最好使用额外的资源来确保其他工作负载不受影响。

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

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

        14
  •  0
  •   Aradhana Mohanty    7 年前

    选择with column name会增加数据库引擎从索引访问数据而不是查询表数据的可能性。

    select*在数据库架构发生更改的情况下,会使系统暴露在意外的性能和功能更改中,因为您要将任何新列添加到表中,即使您的代码不准备使用或呈现该新数据。

        15
  •  0
  •   Lukasz Szozda    6 年前

    还有更实际的原因:金钱。当您使用云数据库并且必须支付处理的数据时,没有任何解释可以读取您将立即丢弃的数据。

    例如: BigQuery :

    查询定价

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

    Control projection - Avoid SELECT * :

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

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

    使用select*是查询数据最昂贵的方法。使用select*时,bigquery会对表中的每一列进行完全扫描。

        16
  •  0
  •   Shaurya Uppal    5 年前

    不使用SELECT*FROM表的原因:

    1. 不必要的输入输出

    2. 增加网络流量

    3. 脆弱的景色

    4. 联接查询中的冲突

    5. 更多应用程序内存

    6. 复制数据时存在风险

    7. 取决于列顺序

    总是使用列名将有助于在大型数据库中始终使用。