使用 select * 的原因是什么?

145

我见过许多人声称在选择查询中应该特别命名每个列。

假设我无论如何都会使用所有列,那么为什么我不使用 SELECT *

即使考虑到这个问题 SQL 查询问题 - 从视图选择 * 还是从视图选择 col1,col2,… colN ,我认为这并不完全相同,因为我从稍微不同的角度来处理这个问题。

我们的一个原则是在时机未到之前不要优化。考虑到这一点,似乎在资源问题或模式基本上固定之前,使用 SELECT * 应该是首选方法。 而我们知道,这不会发生直到开发完全完成。

话虽如此,是否有一个重要的问题不能使用 SELECT *

20个回答

182
“不要过早优化”的引言的精髓在于采用简单直接的代码,然后使用分析工具指出热点,这样您就可以对其进行优化以提高效率。
当您使用“select *”时,无法进行分析,因此您没有编写清晰简洁的代码,并且与该引言的精神相悖。 "select *"是一种反模式。
因此选择列不是过早优化。我脑海中有几件事情...
1.如果在SQL语句中指定列,则在从表中删除该列并执行查询时,SQL执行引擎将出错。 2.您可以更轻松地扫描使用该列的代码。 3.您应始终编写查询以获取最少的信息。 4.正如其他人所提到的,如果使用序号列访问,则永远不应使用"select *"。 5.如果SQL语句连接表,则"select *"会向您提供连接中所有表的所有列。
相关的是,使用“select *”...
1.应用程序使用的列是不透明的 2.DBA及其查询分析器无法帮助您的应用程序性能差 3.更改发生时代码更加脆弱 4.您的数据库和网络正因为带回太多数据而受到影响(I / O) 5.数据库引擎优化很小,因为您带回了所有数据(逻辑)。
编写正确的SQL与编写"select *"一样容易。因此,真正懒惰的人编写正确的SQL,因为他们不想重新访问代码并尝试记住他们在编写代码时正在做什么。他们不想向DBA解释有关每个代码的内容。他们不想向客户解释应用程序运行缓慢的原因。

2
在你的第一部分中,第5点应该写成“select 会从连接中的所有表中获取所有列*”。在你的第二部分中,第2点和第5点不一定是正确的,不应该列为不使用“select *”的原因。 - jimmyorr
1
@uglysmurf - 感谢您的纠正,但是关于2和5 - 虽然它们可能不适用于所有数据库/所有情况下的dba,但我认为它们对于大多数情况来说仍然很重要和有效,并且会保留它们。使用“select *”从未使dba的工作更轻松。 - Robert Paulson
11
我认为第三点(脆弱的代码)并不完全正确。根据实现方式,"Select *"可能会使代码更加健壮,但我不认为它会让代码变得更加脆弱。 - JohnFx
3
@JohnFx,我想你对“脆弱”的定义与众不同。通常情况下,“脆弱”指的是“易碎”。由于每个代码片段都使用不同的列,导致无法确定或难以找到依赖项,这意味着我无法轻松地在数据层面上进行任何更改而不需要进行完整回归测试... 这看起来就很脆弱。 - Robert Paulson
9
关于脆性问题,我担心这会陷入对我使用“脆”的选择的语义问题。我的最后一句话是说无论如何都没有什么区别。唯一的情况是重命名/删除列。你只是将中断从执行 SQL 时(显式)移动到在消耗结果时中断。查询结果被消耗的方式可能会有所不同,代码可能会默默失败,但SQL执行引擎肯定会因为无效的SQL而失败。那么,select * 对你有用吗?在我看来,对于数据库问题,更靠近数据库的显式错误更好。谢谢。 - Robert Paulson
显示剩余5条评论

44
如果你的代码依赖于列的特定顺序,那么当表格发生变化时,你的代码将会出错。此外,如果表格中有二进制字段,选择*语句将从表格中提取过多的内容。
现在你使用了所有的列,并不意味着其他人不会向表格中添加额外的列。
该操作还会增加计划执行缓存的开销,因为它必须获取关于表格的元数据来知道哪些列在*中。

4
不错的回答,但我会把“code will break”改成“code MAY break”。这里真正的问题是,“select *” 的使用并不总是会导致破坏性更改。而且当出现破坏时,通常与最终被破坏的使用高度解耦。 - BQ.
4
无论是否使用SELECT *,如果有人在代码中按序引用列,他们都会遇到麻烦。计划执行开销微不足道,在计划被缓存后也无关紧要。 - MusiGenesis
2
那么程序员的错误在于编写依赖于列序列的代码。你永远不需要这样做。 - dkretz
1
@doofledorfer -永远不要说永远。访问序数列更快,有时很实用。使用select *比使用序数访问更容易出错。 - Robert Paulson

24

主要原因之一是,如果您从表中添加/删除列,则任何进行SELECT *调用的查询/存储过程都将获取比预期更多或更少的数据列。


3
无论如何都不应编写依赖于返回列数的代码。 - dkretz
4
但是,每个人都在编写需要程序员知道返回哪些数据的代码。如果SELECT *中隐藏了您要查找的列名,您无法使用Ctrl + F进行搜索。 - Lotus Notes

18
  1. 间接地违反了有关尽可能使用严格类型的模块化规则。显式通常更好。

  2. 即使现在您需要表中的每一列,以后可能还会添加更多列,每次运行查询时都会被提取下来,这可能会损害性能。这会损害性能,因为:

    • 您正在通过网络传输更多数据;
    • 因为您可能会破坏优化器直接从索引中提取数据的能力(对于涉及所有部分索引列的查询),而不是在表本身进行查找。

何时使用 select *

当您明确需要表中的每一列时,而不是需要您编写查询时存在的每一列。例如,如果您正在编写一个需要显示表的全部内容的数据库管理应用程序(无论内容是什么),可以使用此方法。


1
另一个使用 SELECT * 的时机是在使用数据库客户端进行测试查询时。 - cdmckay
这似乎是在问题的背景下一个奇怪的例外情况。除了节省一些打字工作外,这样做测试查询有什么好处? - JohnFx
1
同时,SELECT * FROM (SELECT a, b, c FROM table) 也是可以的。 - kmkaplan

13

有几个原因:

  1. 如果数据库中的列数改变,而你的应用程序期望有一定数量的列......
  2. 如果数据库中的列顺序改变,而你的应用程序期望它们按照一定的顺序排列......
  3. 内存开销。8个不必要的INTEGER列会增加32字节的浪费空间。这听起来可能不多,但这是每个查询都会发生的,并且INTEGER是较小的列类型之一......额外的列更可能是VARCHAR或TEXT列,这更容易累积。
  4. 网络开销。与内存开销相关:如果我发出30,000个查询并且有8个不必要的INTEGER列,那么我就浪费了960KB的带宽。VARCHAR和TEXT列可能要大得多。

注意:上面的示例中我选择了INTEGER,因为它们有固定的4字节大小。


1和2会是代码异味,而3和4听起来像是过早优化。 - NikkyD

7
如果您的应用程序使用SELECT *获取数据,并且数据库中的表结构发生了更改(例如删除了某个列),则您在引用缺失字段的每个地方都会导致应用程序失败。如果您查询中包括所有列,您的应用程序仅会在最初获取数据的一个地方出现问题,这样修复起来就更容易。
话虽如此,有许多情况下SELECT *是可取的。其中一个情况是我经常遇到的情况,即我需要将整个表复制到另一个数据库中(例如从SQL Server到DB2)。另一个情况是编写用于通用显示表格的应用程序(即没有了解任何特定表格的情况下)。

问题不在于“是否选择*是可取的”,因此你回答的第二部分不相关。问题陈述称使用“select *”应该是首选,这当然是完全的胡说八道。 - Robert Paulson
是的,我的第二部分与此无关。OQ更改了问题以说明SELECT *更可取,是有点不合适的。 - MusiGenesis
啊,对不起 - 在你回答后问题的方向改变了。 - Robert Paulson
没关系,就连莫扎特也是一个编辑器(https://dev59.com/0XVC5IYBdhLWcg3wcgqd)。我的原始帖子暗示了使用SELECT *会导致同类相食的情况 :) - MusiGenesis

3

当我在 SQL Server 2005 中的视图中使用 select * 时,我注意到了一种奇怪的行为。

运行下面的查询,你就会明白我的意思。

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','c1'
union all select 'a2','b2','c2'
union all select 'a3','b3','c3'

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vStartest]'))
DROP VIEW [dbo].[vStartest]
go
create view dbo.vStartest as
select * from dbo.starTest
go

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vExplicittest]'))
DROP VIEW [dbo].[vExplicittest]
go
create view dbo.[vExplicittest] as
select a,b,c from dbo.starTest
go


select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicitTest

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [D] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','d1','c1'
union all select 'a2','b2','d2','c2'
union all select 'a3','b3','d3','c3'

select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicittest

比较最后两个SELECT语句的结果。我相信你会看到一个由Select *引用列索引而不是名称的结果。
如果重新构建视图,它将再次正常工作。
编辑
我已经添加了一个单独的问题,*在SQL Server 2005中,“select * from table” vs“select colA,colB等from table”的有趣行为*,以更详细地研究该行为。

2

SELECT * 并不总是邪恶的。至少在我看来是这样的。我经常使用它来动态查询返回整个表,加上一些计算字段。

例如,我想从一个“普通”表中计算地理几何图形,即没有任何几何字段但包含坐标字段的表。 我使用postgresql及其空间扩展postgis。但原则适用于许多其他情况。

以下是一个例子:

  • 一个带有标记为x、y、z的字段中存储坐标的位置表:

    CREATE TABLE places (place_id integer, x numeric(10, 3), y numeric(10, 3), z numeric(10, 3), description varchar);

  • 让我们输入一些示例值:

    INSERT INTO places (place_id, x, y, z, description) VALUES
    (1, 2.295, 48.863, 64, '巴黎,星形广场'),
    (2, 2.945, 48.858, 40, '巴黎,埃菲尔铁塔'),
    (3, 0.373, 43.958, 90, '孔多姆,圣皮埃尔大教堂');

  • 我想能够使用某些GIS客户端映射此表的内容。正常的方法是向表中添加一个几何字段,并基于坐标构建几何图形。 但我更喜欢获得一个动态查询:这样,当我更改坐标(更正、更精确等)时,映射的对象实际上会动态移动。 因此,以下是带有 SELECT * 的查询:

    CREATE OR REPLACE VIEW places_points AS
    SELECT *,
    GeomFromewkt('SRID=4326; POINT ('|| x || ' ' || y || ' ' || z || ')')
    FROM places;

    有关 GeomFromewkt() 函数的使用,请参阅 postgis。

  • 以下是结果:

    SELECT * FROM places_points;

 place_id |   x    |   y     |   z     |          description           |                            geomfromewkt                            
----------+--------+---------+---------+--------------------------------+--------------------------------------------------------------------  
        1 | 2.295  | 48.863  | 64.000  | 巴黎,凯旋门广场               | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040  
        2 | 2.945  | 48.858  | 40.000  | 巴黎,埃菲尔铁塔              | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
        3 | 0.373  | 43.958  | 90.000  | 孔多姆,圣皮埃尔大教堂         | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 行)

最右侧的一列可以被任何GIS程序用来正确地映射这些点。

  • 如果将来表中添加了一些字段:不用担心,我只需要再次运行相同的VIEW定义即可。

我希望VIEW的定义可以保持“原样”,使用通配符(*),但遗憾的是它并非如此:这是PostgreSQL内部存储它的方式:

SELECT places.place_id, places.x, places.y, places.z, places.description, geomfromewkt(((((('SRID=4326; POINT ('::text || places.x) || ' '::text) || places.y) || ' '::text) || places.z) || ')'::text) AS geomfromewkt FROM places;


2

当你指定列时,你也在将自己与特定的列绑定在一起,使自己变得不够灵活,让Feuerstein在任何地方都会翻滚。这只是一个想法。


1
我完全不知道Feuerstein是谁。尝试了谷歌搜索,发现有一位心理学家、一个电视角色和一个博主,所以我能想到的最好的解释就是一个笑话。 - NotMe
PL/SQL的O'Reilly书籍作者。尝试谷歌搜索“feuerstein sql”而不是仅仅搜索“feuerstein”。 - orbfish

2
你可能需要连接两张表,并从第二张表使用A列。如果之后你在第一张表中添加了名字相同但含义可能不同的A列,你很可能会得到来自第一张表而不是第二张表的值。如果你明确指定要选择的列,这种情况就不会发生。
当然,有时候指定列也会导致bug,如果你忘了将新列添加到每个select子句中。如果新列不是每次执行查询都需要,那么在发现bug之前可能需要一些时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接