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


136

我见过很多人声称,您应该在选择查询中专门为想要的每一列命名。

假设我仍然要使用所有列,为什么我不使用SELECT *

即使考虑问题* SQL查询-从视图*选择*或从视图 * 选择col1,col2,…colN,我也不认为这是完全相同的副本,因为我正从稍微不同的角度来解决这个问题。

我们的原则之一是在优化之前就不进行优化。考虑到这一点,在被证明是资源问题或架构几乎是固定的之前,似乎SELECT *应该使用首选方法。众所周知,只有完成开发后,这种情况才会发生。

就是说,有一个最重要的问题不使用SELECT *吗?

Answers:


168

不进行过早优化的报价的本质是编写简单明了的代码,然后使用探查器指出热点,然后可以对其进行优化以提高效率。

当使用select *时,将无法进行概要分析,因此,您没有在编写清晰明了的代码,并且背离了报价的精神。select *是反模式。


因此,选择列并不是过早的优化。我头顶上有几件事....

  1. 如果在SQL语句中指定列,则从表中删除该列并执行查询时,SQL执行引擎将出错。
  2. 您可以更轻松地在使用该列的位置扫描代码。
  3. 您应该始终编写查询以带回最少的信息。
  4. 正如其他人所述,如果您使用序数列访问,则永远不要使用select *
  5. 如果您的SQL语句联接表,则选择*将为您提供联接中所有表的所有列

结果是使用select *...

  1. 应用程序使用的列是不透明的
  2. DBA及其查询分析器无法帮助您降低应用程序的性能
  3. 发生更改时,代码更加脆弱
  4. 您的数据库和网络正在遭受痛苦,因为它们带来了太多的数据(I / O)
  5. 数据库引擎优化是最小的,因为您要带回所有数据(逻辑上)。

编写正确的SQL与编写脚本一样容易Select *。因此,真正的懒人会编写适当的SQL,因为他们不想重新访问代码,而是想记住自己在执行操作时在做什么。他们不想向DBA解释每一段代码。他们不想向客户解释为什么应用程序像狗一样运行。


2
在您的第一部分中,第5点应为“ select *为您提供联接中所有表的所有 ”。在第二部分中,点#2和#5不一定是正确的,并且不应作为不使用“选择*”的原因列出。
jimmyorr,2009年

1
@uglysmurf-感谢您的纠正,但对于2和5-尽管它们不一定在所有情况下都适用于所有数据库/ dba,但我认为它们在大多数情况下都是重要且有效的,并将保留下去。使用'select *'从未使dba的工作变得容易。
罗伯特·保尔森

11
我认为#3(脆性代码)不是真的。根据实现的不同,Select *可能会使它变少,但我不知道它怎么可能更脆弱。
JohnFx

2
@JohnFx,我想您对脆性的定义有所不同。脆性通常被定义为“容易断裂”。由于每段代码将使用不同的列而具有未知或难以找到的依赖关系,这意味着我无法在没有完全回归的情况下轻松地在数据级别更改任何内容..这似乎很脆弱。
罗伯特·保尔森

9
@mavnn,有脆性,我担心这是我选择脆性一词时演变成的语义问题。我的最后一句话是说无论如何它没有什么区别。唯一的情况是重命名/删除列。您只是将中断从执行sql(显式)开始到消耗结果时中断。使用查询结果的方式可能会有所不同,并且代码可能会或可能不会默默地失败,但是sql执行引擎肯定会因无效的sql而失败。那么选择*对您有帮助吗?对于数据库问题,IMO越接近数据库的显式故障越好。Thx
罗伯特·保尔森

42

如果您的代码依赖于按特定顺序排列的列,则对表进行更改时,代码将中断。另外,当您选择*时,可能会从表中获取过多内容,尤其是在表中存在二进制字段的情况下。

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

这也增加了计划执行缓存的开销,因为它必须获取有关表的元数据才能知道*中的列。


4
好的答案,但是我将“代码将中断”更改为“代码可能中断”。这是真正的麻烦,“ select *”的使用不会总是产生重大变化。当中断确实发生时,通常与最终中断的使用高度脱钩。
BQ。

4
如果有人通常在其代码中引用列,则无论是否使用SELECT *,他们都会遇到麻烦。计划执行的开销很小,一旦计划被缓存就无所谓了。
MusiGenesis

1
然后,程序员的错误在于编写取决于列顺序的代码。您根本不需要这样做。
dkretz

1
@doofledorfer-永不言败。访问序数列的速度更快,并且有时很实用。与使用顺序访问相比,使用select *是一个更大的错误。
罗伯特·保尔森

23

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


3
无论如何,您绝不应该编写依赖于返回的列数的代码。
dkretz

4
但是每个人都在编写代码,要求程序员知道哪些数据要返回。如果列名隐藏在SELECT *中,则无法Ctrl + F列名。
Lotus Notes

17
  1. 以一种a回的方式,您打破了关于尽可能使用严​​格类型的模块化规则。显式通用性更好。

  2. 即使您现在需要表中的每一列,以后也可以添加更多列,每次您运行查询时都会将其下拉,这可能会降低性能。这会损害性能,因为

    • 您正在通过网络提取更多数据;和
    • 因为您可能无法使用优化程序将数据直接从索引中拉出的能力(用于查询所有属于索引的列),而不是在表本身中进行查找

何时使用选择*

当您明确需要表中的每一列时,而不是需要表中编写查询时已存在的每一列。例如,如果编写的DB管理应用程序需要显示表的全部内容(无论它们碰巧是什么),则可以使用该方法。


1
另一个使用时间是使用SELECT *db客户端进行测试查询时。
cdmckay,2009年

考虑到问题的背景,这似乎是一个奇怪的例外。除了保存某些类型之外,对测试查询执行此操作还有什么好处?
JohnFx

SELECT * FROM(SELECT a,b,c FROM表)也可以。
kmkaplan

12

有几个原因:

  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)。另一个是编写的用于一般显示表的应用程序(即,不了解任何特定表)。


问题不是“是否选择过*理想”,因此答案的第二部分无关紧要。该问题指出,最好使用“选择*”,这当然是完全错误。
罗伯特·保尔森

是的,我的第二部分无关紧要。OQ将问题更改为状态SELECT *是更可取的,是的,这有点像个疯子。
MusiGenesis

是的,很抱歉-问题在您回答后改变了方向。
罗伯特·保尔森

没关系。甚至Mozart还是一名编辑(stackoverflow.com/questions/292682/…)。我的原始帖子建议使用SELECT *导致自相残杀。:)
MusiGenesis

3

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

运行以下查询,您将明白我的意思。

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 *通过索引而不是名称引用列的结果。

如果您重建视图,它将再次正常运行。

编辑

我添加了一个单独的问题,* “从表中选择*”与“从表中选择colA,colB等”与SQL Server 2005中的有趣行为 *以便更详细地研究该行为。


2

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

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


2

我了解过早优化的方向,但这实际上只是到了一定程度。目的是在开始时避免不必要的优化。您的表没有索引吗?您会使用nvarchar(4000)来存储邮政编码吗?

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


2

当您指定列时,您还将自己束缚在一组特定的列中,并使自己的灵活性降低,从而使Feuerstein可以随意翻转,无论他身在何处。只是一个想法。


1
我完全不知道谁是Feuerstein。尝试使用Google搜索,找到了一名心理学家,一名电视角色和一名博客作者,所以我能想到的最好的事情就是开玩笑。
NotMe 2012年

O'Reilly关于PL / SQL的书的作者。尝试谷歌搜索“ feuerstein sql”,而不只是“ feuerstein”。
orbfish 2012年

2

SELECT *并不总是邪恶的。我认为,至少。对于返回整个表以及一些计算字段的动态查询,我经常使用它。

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

一个例子:

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

    CREATE TABLE位置(place_id整数,x数值(10、3),y数值(10、3),z数值(10、3),描述varchar);

  • 让我们为它提供一些示例值:

    插入地方(place_id,x,y,z,description)值
    (1,2.295,48.863,64,'巴黎,l'Étoile'),
    (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;要点(' || X || '' ||ý|| '' || ||Ž ')')
    FROM场所;

    有关GeomFromewkt()函数的用法,请参见postgis。

  • 结果如下:

    SELECT * FROM place_points;

place_id | x | y | z | 描述| 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 lignes)

现在,任何GIS程序都可以使用最右边的列来正确映射点。

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

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

选择place.place_id,places.x,places.y,places.z,places.description,geomfromewkt(((((((''SRID = 4326; POINT(':: text || places.x)||'': :text)|| place.y)||'':: text)|| place.z)||')':: text)AS geomfromewkt FROM地方;


1

即使您使用每一列,但按数字索引寻址行数组,以后再添加另一行时也会遇到问题。

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


1

仅选择所需的列可将数据集保留在内存中,从而使您的应用程序更快。

同样,许多工具(例如存储过程)也缓存查询执行计划。如果以后添加或删除列(如果选择关闭视图尤其容易),则该工具在未获得期望的结果时通常会出错。


1

它使您的代码更加模棱两可,更难以维护。因为您要向域中添加多余的未使用数据,但尚不清楚您打算使用哪个,哪些不是。(这也表明您可能不知道或不在意。)


1

要直接回答您的问题:当代码使您的代码更不易更改对基础表的更改时,请不要使用“ SELECT *”。仅当对表进行直接影响程序要求的更改时,代码才应中断。

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


1

我不使用SELECT *只是因为很高兴看到并知道我要检索的字段。


1

通常在视图内部使用'select *'很不好,因为如果表列发生更改,您将不得不重新编译视图。更改视图的基础表列,对于不存在的列,您将得到一个错误,直到返回并重新编译。


1

可以做,exists(select * ...)因为它永远不会扩展。否则,只有在浏览带有临时选择语句的表时,或者如果您在上面定义了CTE并且希望每个列都不要再次键入它们时,它才真正有用。


1

只是添加一件事,其他人都没有提到。Select *返回所有列,某人可能稍后会添加一列,您不一定希望用户能够看到这些列,例如上次更新数据的人或时间戳记,或者指出只有管理者才能看到所有用户,等等。

此外,在添加列时,应检查并考虑对现有代码的影响,以根据列中存储的信息查看是否需要更改。通过使用select *,该评论通常会被跳过,因为开发人员会认为一切都不会中断。实际上,似乎没有任何东西可能会中断,但是查询现在可能开始返回错误的东西。仅仅因为没有任何明显的中断,并不意味着不应该对查询进行任何更改。


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.