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

时间:2008-11-26 16:22:49

标签: sql

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

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

即使考虑了问题* SQL query - Select * from view or Select col1, col2, … colN from view *,我也不认为这是完全重复的,因为我从一个略微不同的角度来看待这个问题。

我们的一个原则是不要在它之前进行优化。考虑到这一点,似乎使用SELECT *应该是首选方法,直到它被证明是资源问题或者模式几乎是一成不变的。正如我们所知,在开发完成之前不会发生这种情况。

那就是说,不使用SELECT *是否存在一个最重要的问题?

20 个答案:

答案 0 :(得分:161)

不过早优化的引用的本质是寻求简单直接的代码,然后使用分析器来指出热点,然后您可以优化这些热点以提高效率。

当你使用select *时,你无法分析,因此你不会写清楚&直截了当的代码,你违背了引用的精神。 select *是一种反模式。


因此选择列并不是一个不成熟的优化。我头顶的一些事情......

  1. 如果在SQL语句中指定列,则如果从表中删除该列并执行查询,则SQL执行引擎将出错。
  2. 您可以更轻松地扫描正在使用该列的代码。
  3. 您应该始终编写查询以恢复最少量的信息。
  4. 正如其他人所说,如果您使用序数列访问,则不应使用select *
  5. 如果您的SQL语句连接表,则select *为您提供连接中所有表的所有列
  6. 推论是使用select * ...

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

    6. 编写正确的SQL与编写Select *一样简单。因此,真正懒惰的人会编写正确的SQL,因为他们不想重新访问代码并尝试记住他们在执行代码时所做的事情。他们不想向DBA解释每一点代码。他们不想向客户解释为什么应用程序像狗一样运行。

答案 1 :(得分:42)

如果您的代码依赖于特定顺序的列,那么当表格发生更改时,您的代码就会中断。此外,当您选择*时,您可能会从表中获取太多内容,特别是如果表中有二进制字段。

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

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

答案 2 :(得分:23)

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

答案 3 :(得分:16)

  1. 以迂回的方式打破了关于使用的模块化规则 尽可能严格打字。明确几乎是普遍的 更好。

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

    • 您正在通过电线提取更多数据;和
    • 因为你可能会破坏优化器将数据从索引中拉出来的能力(对于列中所有列的查询都是如此)而不是 在表格中查找
  3. 何时使用select *

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

答案 4 :(得分:12)

有几个原因:

  1. 如果数据库中的列数发生变化且您的应用程序希望有一定数量的数据......
  2. 如果数据库中列的顺序发生变化,而您的应用程序希望它们按特定顺序...
  3. 内存开销。 8个不必要的INTEGER列会增加32个字节的浪费内存。这听起来不是很多,但这是针对每个查询而INTEGER是小列类型之一......额外的列更可能是VARCHAR或TEXT列,这样可以更快地添加。
  4. 网络开销。与内存开销有关:如果我发出30,000个查询并且有8个不必要的INTEGER列,我浪费了960kB的带宽。 VARCHAR和TEXT列可能会大得多。
  5. 注意:我在上面的示例中选择了INTEGER,因为它们具有4字节的固定大小。

答案 5 :(得分:7)

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

话虽如此,有许多情况需要SELECT *。一种是我一直遇到的情况,我需要将整个表复制到另一个数据库(例如,SQL Server到DB2)。另一个是一般写入显示表的应用程序(即不知道任何特定的表)。

答案 6 :(得分: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

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

如果重建视图,它将再次正常工作。

修改

我添加了一个单独的问题,* “select * from table” vs “select colA, colB, etc. from table” interesting behaviour in SQL Server 2005 *以更详细地研究这种行为。

答案 7 :(得分: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地点(place_id,x,y,z,说明) VALUES
    (1,2.295,48.863,64,'Paris,Place de l''Étoile'),
    (2,2.945,48858,40,'巴黎,埃菲尔铁塔'),
    (3,0.373,43.958,90,'避孕套,CathédraleSt-Pierre');

  • 我希望能够使用某个GIS客户端映射此表的内容。通常的方法是向表中添加几何字段,并根据坐标构建几何。 但我更希望得到一个动态查询:这样,当我更改坐标(更正,更准确等)时,映射的对象实际上是动态移动的。 所以这是使用 SELECT *

    的查询

    创建或替换视图places_points AS
    SELECT *,
    GeomFromewkt('SRID = 4326; POINT('|| x ||''|| y ||''|| z ||')')
    来自地方;

    请参阅postgis,了解GeomFromewkt()函数的用法。

  • 结果如下:

    SELECT * FROM places_points;

 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的定义可以保持“原样”,使用*,但实际情况并非如此:这是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;

答案 8 :(得分:2)

当你指定专栏时,你也会将自己绑定到一组特定的专栏并使自己变得不那么灵活,让Feuerstein继续进行,好吧,无论他是谁。只是一个想法。

答案 9 :(得分:2)

我理解你在过早优化方面的目标,但这只是一个点。目的是在开始时避免不必要的优化。你的桌子没有索引吗?你会用nvarchar(4000)来存储邮政编码吗?

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

答案 10 :(得分:2)

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

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

答案 11 :(得分:1)

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

您的应用程序应该利用Relational access提供的抽象层。

答案 12 :(得分:1)

它使您的代码更加模糊,更难以维护;因为您正在向域中添加额外的未使用数据,并且不清楚您的目标是什么,哪些不是。 (这也表明你可能不知道或关心。)

答案 13 :(得分:1)

当你做exists(select * ...)时它没关系,因为它永远不会被扩展。否则,它只有在探索具有临时选择状态的表时才有用,或者如果您在上面定义了CTE,并且您希望每列都不再将它们全部输入。

答案 14 :(得分:1)

我不使用SELECT *只是因为很高兴看到并知道我正在检索哪些字段。

答案 15 :(得分:1)

通常在视图中使用'select *'是不好的,因为在表列更改时将强制重新编译视图。更改视图的基础表列,在返回并重新编译之前,您将收到不存在列的错误。

答案 16 :(得分:1)

只是添加一个没人提到的东西。 Select *会返回所有列,有些人可能会在以后添加一个列,您不一定希望用户能够看到诸如上次更新数据的人或时间戳或只有经理应该看不到所有用户的注释等等。

此外,在添加列时,应检查对现有代码的影响,并根据列中存储的信息查看是否需要进行更改。通过使用select *,通常会跳过该审核,因为开发人员会认为没有任何内容会中断。事实上,没有任何东西可能会明确地显示出来,但查询可能现在开始返回错误的东西。仅仅因为没有明确地中断,并不意味着不应该对查询进行更改。

答案 17 :(得分:1)

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

此外,许多工具(例如存储过程)也会缓存查询执行计划。如果您以后添加或删除列(如果您选择关闭视图,则特别容易),当该工具无法获得预期的结果时,该工具通常会出错。

答案 18 :(得分:1)

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

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

答案 19 :(得分:0)

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