【问题标题】:Why is using '*' to build a view bad?为什么使用“*”构建视图不好?
【发布时间】:2010-09-20 17:15:03
【问题描述】:

为什么使用 '*' 构建视图不好?

假设您有一个复杂的连接,并且所有字段都可能在某处使用。

然后你只需要选择需要的字段。

SELECT field1, field2 FROM aview WHERE ...

视图“aview”可以是SELECT table1.*, table2.* ... FROM table1 INNER JOIN table2 ...

如果 table1 和 table2 中的 2 个字段具有相同的名称,我们会遇到问题。

这仅仅是在视图中使用 '*' 不好的原因吗?

使用“*”,您可以在不同的上下文中使用视图,因为信息就在那里。

我错过了什么?

问候

【问题讨论】:

    标签: sql view


    【解决方案1】:

    这是因为您并不总是需要每一个变量,并且还要确保您正在考虑您具体需要什么。

    例如,在您的网站上构建用户列表时,将所有散列密码从数据库中取出是没有意义的,因此选择 * 将是无效的。

    【讨论】:

      【解决方案2】:

      我认为软件中没有太多“糟糕”的地方,但有很多东西被滥用了:-)

      您给出的示例是 * 可能无法满足您期望的原因,我认为还有其他原因。例如,如果基础表发生更改,可能会添加或删除列,使用 * 的视图将继续有效,但可能会破坏任何使用它的应用程序。如果您的视图明确命名了列,那么在进行架构更改时有人会发现问题的可能性更大。

      另一方面,你实际上可能希望你的观点是愉快的 接受对基础表的所有更改,在这种情况下 * 将 做你想要的。

      更新:我不知道 OP 是否考虑了特定的数据库供应商,但现在很明显,我的最后一句话并不适用于所有类型。感谢 user12861 和 Jonny Leeds 指出了这一点,很抱歉我花了 6 年时间来编辑我的答案。

      【讨论】:

      • 另外,使用视图的代码不应该使用*,所以在视图中使用*可能是相当合理的,并且使用视图的代码有责任确保它不是使用 *.
      • 最后的评论是完全错误的(至少在 sql server 中)——相反的情况——对基础表的更改被默默地忽略。这就是@user12861 提到的。在 sql server 中,任何使用 select * 的视图都需要在底层表更改时删除并重新创建,否则它将默默地不显示所有列。这是一种非常糟糕的故障形式,因为它不太可能在您定期从头开始重建数据库的开发环境中被发现。添加列时也很容易忘记删除和重建表上的所有视图
      • 查看此链接了解详情coding.abel.nu/2013/01/…
      【解决方案3】:

      曾几何时,我使用

      针对另一个数据库(在同一服务器上)中的表创建了一个视图
      Select * From dbname..tablename
      

      然后有一天,在目标表中添加了一列。在重新部署之前,视图开始返回完全错误的结果。


      完全错误:没有行。

      这是在 Sql Server 2000 上。

      我推测这是因为视图已经捕获了 syscolumns 值,即使我使用了 *。

      【讨论】:

        【解决方案4】:

        在任何产品中使用“*”都是不好的。它非常适合一次性查询,但在生产代码中,您应该始终尽可能明确。

        特别是对于视图,如果基础表添加或删除了列,则在重新编译之前,视图要么是错误的要么是损坏的。

        【讨论】:

          【解决方案5】:

          *”不仅在视图中而且在查询中存在风险的另一个原因是列可以更改名称或更改基础表中的位置。使用通配符意味着您的视图无需更改即可轻松适应此类更改。但是,如果您的应用程序在结果集中按位置引用列,或者如果您使用的动态语言返回以列名作为键的结果集,您可能会遇到难以调试的问题。

          我始终避免使用通配符。这样,如果列更改名称,我会立即在视图或查询中收到错误,并且我知道在哪里修复它。如果列在基础表中的位置发生变化,则指定视图或查询中列的顺序可以弥补这一点。

          【讨论】:

          • 请阅读我的其他答案,基础表的更改并不总是反映,至少在 SQL 服务器上。
          【解决方案6】:

          虽然这里的许多 cmets 都非常好,并且引用了在查询中使用通配符的一个常见问题,例如如果基础表发生更改会导致错误或不同的结果,但另一个尚未涉及的问题是优化。提取表的每一列的查询往往不如只提取您实际需要的那些列的查询那么有效。诚然,有时您需要每一列,而主要的 PIA 必须全部引用它们,尤其是在大表中,但如果您只需要一个子集,为什么要用比您需要的更多的列来阻止查询。

          【讨论】:

          • 这是我不使用 SELECT * 的主要原因。如果您执行其他危险的操作,例如 SELECT * 与 JOIN 或通过索引而不是名称引用列,则其他原因主要相关。呃,当然这些是危险的组合。但 select * 本身并不是邪恶的。
          • 我同意这通常并不危险。在对数据库进行数据检查时,我经常使用 SELECT *。但是,在编写生产代码时,我倾向于避免使用 SELECT *,以避免遇到上述问题并继续培养良好的编程习惯。
          • 使用* 的查询可以在底层表发生变化时同样健壮,如果您以字典/关联数组/对象的形式访问行而不是使用数组索引。 Dictionary/assoc-array/objects 可能比使用数组索引要慢,但在很多情况下可读性胜过速度。我将大多数 SQL 查询编写为 SELECT * 并使用字典/关联数组/对象,除非可以显示特定代码存在性能问题。在大多数代码中,性能问题根本不存在。
          【解决方案7】:

          通常使用 *.一些代码认证引擎将此标记为警告,并建议您仅明确引用必要的列。使用 * 可能会导致性能问题,因为您可能只需要一些列而不是全部。但是,另一方面,在某些情况下使用 * 是理想的。想象一下,无论如何,使用您提供的示例,对于此视图 (aview),您将始终需要这些表中的所有列。将来,当添加列时,您无需更改视图。根据您处理的情况,这可能是好是坏。

          【讨论】:

            【解决方案8】:

            在视图内使用SELECT * 不会在视图外使用列时不会产生太多性能开销——优化器会优化它们; SELECT * FROM TheView 可能会浪费带宽,就像您通过网络连接拉更多列时一样。

            事实上,我发现链接了我的数据仓库中大量大表的几乎所有列的视图根本没有引入任何性能问题,即使从视图外部请求的这些列相对较少。优化器处理得很好,并且能够很好地将外部过滤条件下推到视图中。

            但是,由于上述所有原因,我很少使用SELECT *

            我有一些业务流程,其中许多 CTE 相互叠加,有效地从派生列的派生列构建派生列(希望有一天随着业务合理化和简化这些计算而重构),并且在这种情况下,我每次都需要删除所有列,并且我使用SELECT * - 但SELECT * 不用于基础层,仅在第一个 CTE 和最后一个 CTE 之间使用。

            【讨论】:

              【解决方案9】:

              我认为这取决于您使用的语言。当语言或 DB 驱动程序返回结果的 dict(Python、Perl 等)或关联数组(PHP)时,我更喜欢使用 select *。如果您通过名称而不是数组中的索引来引用列,它会使您的代码更容易理解。

              【讨论】:

              • 如果字段顺序被修改,关联数组继续工作。
              【解决方案10】:

              这些其他答案都有好点,但至少在 SQL Server 上它们也有一些错误点。试试这个:

              create table temp (i int, j int)
              go
              create view vtemp as select * from temp
              go
              insert temp select 1, 1
              go
              alter table temp add k int
              go
              insert temp select 1, 1, 1
              go
              select * from vtemp
              

              SQL Server 在添加时不会了解“新”列。这取决于你想要什么,这可能是好事也可能是坏事,但无论哪种方式,依赖它可能都不好。所以避免它似乎是个好主意。

              对我来说,这种奇怪的行为是避免在视图中选择 * 的最令人信服的原因。

              cmets 告诉我 MySQL 具有类似的行为,而 Oracle 没有(它将了解表的更改)。这种不一致对我来说是不在视图中使用 select * 的更多理由。

              【讨论】:

              • MySQL 中的相同行为 - 新列不会成为视图的一部分。显然,通配符在创建视图时转换为列列表。重命名基表中的列也会使视图不可用。
              • Oracle 不能这样工作。该视图将因表更改而失效,并在下次访问时使用新列重新编译。
              【解决方案11】:

              似乎没有其他人提到它,但在 SQL Server 中,您还可以使用schemabinding 属性设置您的视图。

              这可以防止修改任何会影响视图定义的基表(包括删除它们)。

              这在某些情况下可能对您有用。我意识到我没有完全回答你的问题,但我想我还是会强调它。

              【讨论】:

                【解决方案12】:

                SQL 查询基本上是程序员设计的用于某些上下文的功能单元。为了长期稳定性和可支持性(可能由您以外的其他人),功能单元中的所有内容都应该有目的,并且应该合理地明显(或记录)为什么存在 - 特别是数据的每个元素。

                如果两年后我需要或希望改变您的查询,我希望能彻底了解它,然后才能确信我可以搞砸它。这意味着我需要了解为什么所有列都被调出。 (如果您试图在多个上下文中重用查询,则这一点更明显。出于类似的原因,这通常是有问题的。)如果我要在输出中看到与某些目的无关的列,我很确定我不明白它做了什么,为什么,以及改变它会产生什么后果。

                【讨论】:

                • 这仅表明很难将*-ed 查询改回显式命名查询;它没有说明为什么*-ed 查询不好。 IMO,*-ed 查询本身并没有什么坏处;这只是意味着你不需要太细粒度。
                • 我不确定在依赖关系确定的上下文中“太细粒度”是什么意思。除非你已经决定依赖是不重要的。
                【解决方案13】:

                如果您有使用 select * 的连接,则自动意味着您返回的数据比您需要的更多,因为连接字段中的数据会重复。这是对数据库和网络资源的浪费。

                如果您天真地使用调用其他视图的视图,则使用 select * 会使它们的性能更差(这种技术本身不利于性能,调用不需要的多个列会使情况变得更糟)。

                【讨论】:

                  【解决方案14】:

                  SQL Server 上的情况实际上比@user12861 所暗示的答案更糟糕:如果您对多个表使用SELECT *,则将列添加到查询中早期引用的表实际上会导致您的视图返回以旧列为幌子的新列。请看下面的例子:

                  -- create two tables
                  CREATE TABLE temp1 (ColumnA INT, ColumnB DATE, ColumnC DECIMAL(2,1))
                  CREATE TABLE temp2 (ColumnX INT, ColumnY DATE, ColumnZ DECIMAL(2,1))
                  GO
                  
                  
                  -- populate with dummy data
                  INSERT INTO temp1 (ColumnA, ColumnB, ColumnC) VALUES (1, '1/1/1900', 0.5)
                  INSERT INTO temp2 (ColumnX, ColumnY, ColumnZ) VALUES (1, '1/1/1900', 0.5)
                  GO
                  
                  
                  -- create a view with a pair of SELECT * statements
                  CREATE VIEW vwtemp AS 
                  SELECT *
                  FROM temp1 INNER JOIN temp2 ON 1=1
                  GO
                  
                  
                  -- SELECT showing the columns properly assigned
                  SELECT * FROM vwTemp 
                  GO
                  
                  
                  -- add a few columns to the first table referenced in the SELECT 
                  ALTER TABLE temp1 ADD ColumnD varchar(1)
                  ALTER TABLE temp1 ADD ColumnE varchar(1)
                  ALTER TABLE temp1 ADD ColumnF varchar(1)
                  GO
                  
                  
                  -- populate those columns with dummy data
                  UPDATE temp1 SET ColumnD = 'D', ColumnE = 'E', ColumnF = 'F'
                  GO
                  
                  
                  -- notice that the original columns have the wrong data in them now, causing any datatype-specific queries (e.g., arithmetic, dateadd, etc.) to fail
                  SELECT *
                  FROM vwtemp
                  GO
                  
                  -- clean up
                  DROP VIEW vwTemp
                  DROP TABLE temp2
                  DROP TABLE temp1
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-11-06
                    • 1970-01-01
                    • 2014-09-12
                    相关资源
                    最近更新 更多