为什么SELECT *被认为是有害的?
为什么SELECT *被认为是有害的?
为什么SELECT *
是不好的习惯?如果想要添加一个新的列,这不意味着需要更改的代码更少吗?
我理解在一些数据库中,SELECT COUNT(*)
会导致性能问题,但是如果你真的想要每一列的话呢?
在SELECT语句中,星号字符“*”是指涉及到的表中的所有列的快捷方式。
性能
使用“*”缩写可能会更慢,因为:
- 并非所有字段都被索引,导致全表扫描-效率低下
- 发送“SELECT *”可能会导致全表扫描
- 返回比需要更多的数据
- 使用可变长度数据类型返回尾随列可能会导致搜索开销
维护
在使用“SELECT *”时:
- 对于不熟悉代码库的人来说,必须在能够进行有效更改之前查阅文档以知道返回的列是什么。使代码更易读,减少不熟悉代码的人的歧义和工作可节省更多的时间和精力。
- 如果代码依赖于列顺序,“SELECT *”会隐藏一个等待发生的错误,如果表的列顺序发生了变化。
- 即使在写查询时需要每个列,但将来可能不需要。
- 使用会使分析变得复杂
设计
“SELECT *”是一种反模式:
- 查询的目的不太明显。应用程序使用的列是不透明的
- 它打破了使用严格类型化的模块化规则。明确通常更好。
何时使用“SELECT *”?
当需要涉及的表中的每一列时,而不是在查询编写时存在的每一列时,可以使用“SELECT *”。数据库将内部扩展*为完整的列列表-性能没有差异。
否则,请显式列出在查询中要使用的每个列-最好同时使用表别名。
真的有三个主要原因:
-
将数据传递给消费者时效率低下。 当你使用SELECT *时,经常从数据库检索的列比你的应用程序实际上需要的要多。这会导致更多的数据从数据库服务器移动到客户端,从而减缓访问速度并增加机器负载,同时需要更长时间才能在网络上传输。当某人向底层表添加不需要或不存在的新列时,特别是在原始消费者编写其数据访问时,情况尤其如此。
-
索引问题。考虑一种情况,你想将查询调整到高性能级别。 如果你使用*,并且它返回比你实际需要的列更多的列,则服务器通常会执行比它本来可能更昂贵的检索数据的方法。 例如,你将无法创建一个仅涵盖SELECT列表中的列的索引,而即使你这样做(包括所有列[噩梦般的]),下一个人来了并向底层表添加了一列,导致优化器忽略你优化的覆盖索引,你可能会发现查询的性能会无缘无故地急剧下降。
-
绑定问题。 当你使用SELECT *时,可能从两个不同的表中检索出相同名称的两个列。这通常会导致数据消费者崩溃。想象一下一个查询连接两个表,两个表都包含名为“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 列表中使用某些 ID 或者使用数字1,但依我看这些约定几乎是毫无意义的。你的意思是“计算行数”,而这就是 * 所表示的。大多数查询优化器都足够聪明,知道这一点。(虽然说,老实说,我只知道在 SQL Server 和 Oracle 中这是正确的。)