不使用 select * 的原因是什么?

我见过许多人声称,您应该在选择查询中明确命名所需的每一列。

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

即使考虑到这个问题 * SQL 查询-从视图中选择 * 或从视图中选择 col1,col2,... colN * ,我也不认为这是一个完全重复的问题,因为我是从一个稍微不同的角度来看待这个问题的。

我们的原则之一就是不要提前优化。考虑到这一点,看起来使用 SELECT *应该是 更喜欢方法,直到它被证明是一个资源问题或者模式基本上是固定不变的。正如我们所知,在开发完成之前是不会发生的。

也就是说,是否存在不使用 SELECT *的首要问题?

33551 次浏览

一个主要原因是,如果从表中添加/删除列,任何正在进行 SELECT * 调用的查询/过程现在将获得比预期更多或更少的数据列。

如果代码依赖于列的特定顺序,则当表发生更改时,代码将中断。另外,当您选择 * 时,可能会从表中获取太多信息,特别是在表中有二进制字段的情况下。

仅仅因为您现在使用了所有列,并不意味着其他人不会向表中添加额外的列。

它还增加了计划执行缓存的开销,因为它必须获取有关表的元数据,以了解 * 中的列。

即使您使用每一列,但是通过数字索引处理行数组,如果稍后添加另一行,您也会遇到问题。

所以基本上这是一个可维护性的问题!如果不使用 * 选择器,则不必担心查询。

只选择所需的列可以使内存中的数据集更小,从而使应用程序更快。

此外,还有许多工具(例如存储过程)缓存查询执行计划。如果您稍后添加或删除一个列(如果您选择了一个视图,那么这个工具就特别容易) ,当它没有得到它所期望的结果时,它经常会出错。

有几个原因:

  1. 如果数据库中的列数发生更改,并且应用程序期望有一定数量的..。
  2. 如果数据库中的列顺序发生更改,并且应用程序希望它们按照某种顺序排列..。
  3. 内存开销。8个不必要的 INTEGER 列会增加32字节的浪费内存。这听起来不是很多,但这是针对每个查询的,INTEGER 是小列类型之一... ... 额外的列更有可能是 VARCHAR 或 TEXT 列,这样加起来更快。
  4. 网络开销。与内存开销相关: 如果我发出30,000个查询并且有8个不必要的 INTEGER 列,那么我已经浪费了960kB 的带宽。VARCHAR 和 TEXT 列可能要大得多。

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

如果应用程序使用 SELECT * 获取数据,并且数据库中的表结构发生了更改(比如删除了一个列) ,则应用程序在引用缺失字段的每个地方都会失败。如果在查询中包含所有列,则应用程序将中断最初获取数据的位置(希望如此) ,从而使修复更容易。

也就是说,在许多情况下,SELECT * 是可取的。一种是我一直遇到的情况,需要将整个表复制到另一个数据库中(例如,将 SQLServer 复制到 DB2)。另一个是编写的通用显示表的应用程序(即不知道任何特定的表)。

您可以联接两个表并使用第二个表中的列 A。如果稍后将列 A 添加到第一个表(名称相同,但含义可能不同) ,您很可能会从第一个表获得值,而不是像前面那样从第二个表获得值。如果显式指定要选择的列,则不会发生这种情况。

当然,如果忘记将新列添加到每个 select 子句中,指定列有时也会导致 bug。如果每次执行查询时都不需要新列,那么可能需要一些时间才能注意到错误。

当你做 exists(select * ...)的时候没关系,因为它从来没有被展开过。否则,只有在使用临时选择语句探索表时,或者如果上面定义了 CTE,并且希望每个列都不要重新键入它们时,它才真正有用。

当我在 SQLServer2005视图中使用 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

比较最后2个 select 语句的结果。 我相信您将看到的是 选择 * 按索引引用列而不是按名称引用列的结果。

如果重新生成视图,它将再次正常工作。

剪辑

我加了一个单独的问题 * “ select * from table”与“ select colA,colB,etc from table”在 SQLServer2005中的有趣行为 * 来更详细地研究这种行为。

它使您的代码更加模糊,更加难以维护; 因为您向域添加了额外的未使用数据,并且不清楚哪些是您想要的,哪些不是。(这也表明你可能不知道,或者不在乎。)

为了直接回答您的问题: 不要使用“ SELECT *”,因为它会使您的代码对基础表的更改更加脆弱。只有对表进行了直接影响程序需求的更改时,代码才会中断。

您的应用程序应该利用关系访问提供的抽象层。

我明白你关于过早优化的想法,但这只是一个问题。其目的是在一开始就避免 没必要优化。您的表是否未建立索引?您会使用 nvarchar (4000)来存储邮政编码吗?

正如其他人指出的那样,指定要在查询中使用的每个列还有其他好处(如可维护性)。

不过早优化的本质是寻求简单而直接的代码,那么使用分析器指出热点,然后可以对其进行优化以提高效率。

当你使用 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 解释每一点代码。他们不想向客户解释为什么应用程序像狗一样运行。

  1. 你正在以一种迂回的方式打破关于使用 尽可能严格的打字。显式几乎是普遍的 好多了

  2. 即使您现在需要表中的每一列,也可以添加更多的列 之后,每次运行查询和 可能会影响表现。它会影响表现,因为

    • 您正在通过网络获取更多的数据;
    • 因为您可能会破坏优化器将数据直接从索引中提取出来的能力(对于全部属于索引的列的查询)而不是去做 查找表本身

何时使用 select *

当您显式地需要表中的每一列时,而不是需要在您编写查询时存在的表中的每一列。例如,如果编写一个 DB 管理应用程序,需要显示表的整个内容(不管它们是什么) ,您可能会使用这种方法。

我不使用 SELECT * 仅仅是因为能够查看和知道要检索的字段是什么。

在视图中使用“ select *”通常是不好的,因为在表列发生更改时,您将被迫重新编译视图。更改视图的基础表列将导致不存在列的错误,直到您返回并重新编译。

还有一件别人没提过的事。Select *返回所有的列,有人可能会在后面添加一个列,你不一定希望用户能够看到,比如谁最后更新了数据,或者时间戳,或者只有管理员才能看到不是所有的用户,等等。

此外,在添加列时,应该检查和考虑对现有代码的影响,以查看是否需要根据列中存储的信息进行更改。通过使用 select *,审查将经常被跳过,因为开发人员将假定没有什么会中断。事实上,可能没有什么显式地显示为中断,但查询现在可能开始返回错误的内容。仅仅因为没有显式地中断,并不意味着不应该对查询进行更改。

当您指定列时,您还将自己绑定到一组特定的列中,从而降低了自己的灵活性,使 Feuerstein 无论在哪里都会卷入其中。只是个想法。

因为当您不需要所有字段时,“ select *”会浪费内存。但是对于 sql 服务器,它们的性能是相同的。

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

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

举个例子:

  • 位置表,其中的坐标存储在标记为 x,y,z 的字段中:

    CREATE TABLE 位置(place _ id 整数,x numeric (10,3) ,y numeric (10,3) ,z numeric (10,3) ,description varchar) ;

  • 让我们给它添加一些示例值:

    插入位置(place _ id,x,y,z,description) 价值观
    (1,2.295,48.863,64,‘ Paris,Place de l‘ Étoile’) ,
    (2,2.945,48.858,40,‘ Paris,Tour Eiffel’) ,
    (3,0.373,43.958,90,‘避孕套,圣皮埃尔大教堂’) ;

  • 我希望能够映射这个表的内容,使用一些 GIS 客户端。通常的方法是向表中添加一个几何字段,然后基于坐标构建几何图形。 但是我更喜欢动态查询: 这样,当我改变坐标(更正,更准确等) ,映射的对象实际上是动态移动的。 下面是 选择 * 的查询:

    创建或替换视图 place _ points
    选择 * ,
    GeomFromewkt (‘ SRID = 4326; POINT (’| | x | |’| | y |’| | z | |’)’)
    来自不同地方;

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

  • 结果如下:

    从位置 _ 点选择 * ;

place_id |   x   |   y    |   z    |         description          |                            geomfromewkt
----------+-------+--------+--------+------------------------------+--------------------------------------------------------------------
1 | 2.295 | 48.863 | 64.000 | Paris, Place de l'Étoile     | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040
2 | 2.945 | 48.858 | 40.000 | Paris, Tour Eiffel           | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
3 | 0.373 | 43.958 | 90.000 | Condom, Cathédrale St-Pierre | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 lignes)

最右边的一栏现在可以被任何 GIS 程序用来正确地绘制这些点。

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

我希望 VIEW 的定义可以保持“原样”,使用 * ,但事实并非如此: 这是 postgreql 在内部存储它的方式:

选择 placees.place _ id,placees.x,placees.y,placees.z,placees.description,geomfomewkt (((((‘ SRID = 4326; POINT (’: text | | placees.x) | |’: : text) | |’: : text) | | |’: : text)’: : text)作为 geomfomewkt FROM 位置;