【问题标题】:Is there any reason to worry about the column order in a table?有什么理由担心表格中的列顺序吗?
【发布时间】:2010-10-28 00:41:10
【问题描述】:

我知道你可以用 FIRST 和 AFTER 来改变 MySQL 中的列顺序,但你为什么要费心呢?由于好的查询在插入数据时显式命名列,真的有任何理由关心你的列在表中的顺序吗?

【问题讨论】:

    标签: mysql sql database-table


    【解决方案1】:

    不,SQL 数据库表中列的顺序是完全不相关的——除了显示/打印目的。重新排序列没有意义 - 大多数系统甚至没有提供这样做的方法(除了删除旧表并使用新的列顺序重新创建它)。

    马克

    编辑:来自关系数据库的维基百科条目,这是我清楚地表明列顺序应该永远受到关注的相关部分:

    关系被定义为一组 n 元组。在数学和关系数据库模型中,集合是一个无序项的集合,尽管一些 DBMS 对它们的数据强加了一个顺序。在数学中,元组具有顺序,并且允许重复。 E.F. Codd 最初使用这个数学定义来定义元组。后来,E.F. Codd 的一大见解是,在基于关系的计算机语言中,使用属性名称而不是排序会更方便(通常)。这种见解今天仍在使用。

    【讨论】:

    • 我亲眼看到列差异有很大的影响,所以我不敢相信这是正确的答案。即使投票将其放在首位。小时。
    • 那会在什么 SQL 环境中?
    • 我看到的最大影响是在 Sql Server 2000 上,向前移动外键使某些查询速度提高了 2 到 3 倍。这些查询有大表扫描(1M+ 行),外键有条件。
    • RDBMS 不依赖于表排序除非您关心性能。不同的实现对列的顺序会有不同的性能损失。它可能很大,也可能很小,这取决于实现。元组是理论上的,RDBMS 是实用的。
    • -1。我使用过的所有关系数据库在某种程度上都有列排序。如果您从表中选择 *,您不会倾向于以随机顺序返回列。现在磁盘与显示是不同的辩论。引用数学理论来支持关于数据库实际实现的假设只是一派胡言。
    【解决方案2】:

    必须输入时输出的可读性:

    select * from <table>
    

    在您的数据库管理软件中?

    这是一个非常虚假的原因,但目前我想不出别的。

    【讨论】:

      【解决方案3】:

      一些写得不好的应用程序可能依赖于列顺序/索引而不是列名。他们不应该,但它确实发生了。更改列的顺序会破坏此类应用程序。

      【讨论】:

      • 应用程序开发人员使他们的代码依赖于表中的列顺序,他们的应用程序应该被破坏。但是应用程序的用户不应该被中断。
      【解决方案4】:

      您需要担心列顺序的唯一情况是您的软件是否特别依赖该顺序。这通常是因为开发人员变得懒惰并执行了select *,然后在结果中通过索引而不是名称来引用列。

      【讨论】:

        【解决方案5】:

        列顺序对我调整过的一些数据库有很大的性能影响,包括 Sql Server、Oracle 和 MySQL。此帖有good rules of thumb:

        • 主键列优先
        • 接下来是外键列。
        • 下一个经常搜索的列
        • 以后经常更新的列
        • 可空列最后。
        • 在更频繁使用的可空列之后使用最少的可空列

        性能差异的一个例子是索引查找。数据库引擎根据索引中的某些条件找到一行,并取回一个行地址。现在假设您正在寻找 SomeValue,它在此表中:

         SomeId int,
         SomeString varchar(100),
         SomeValue int
        

        引擎必须猜测 SomeValue 从哪里开始,因为 SomeString 的长度未知。但是,如果您将顺序更改为:

         SomeId int,
         SomeValue int,
         SomeString varchar(100)
        

        现在引擎知道可以在行开始后 4 个字节找到 SomeValue。因此,列顺序会对性能产生相当大的影响。

        编辑:Sql Server 2005 在行首存储固定长度的字段。并且每一行都有一个对 varchar 开头的引用。这完全否定了我上面列出的效果。所以对于最近的数据库,列顺序不再有任何影响。

        【讨论】:

        • @TopBanana:不使用 varchars,这就是它们与普通 char 列的区别。
        • 我不认为表中列的顺序有什么不同 - 它肯定会对您可能创建的索引产生影响,真的。
        • @TopBanana:不确定您是否了解 Oracle,但它不会为 VARCHAR2(100) 保留 100 个字节
        • @Quassnoi:最大的影响是在 Sql Server 上,在具有许多可为空的 varchar() 列的表上。
        • 此答案中的 URL 不再有效,有人有备用网址吗?
        【解决方案6】:

        在上一份工作的 Oracle 培训期间,我们的 DBA 建议将所有不可为空的列放在可空的列之前是有利的......虽然 TBH 我不记得为什么的细节。或者也许只是那些可能会更新的应该在最后? (如果行扩展,可能会推迟移动行)

        一般来说,应该没什么区别。正如您所说,查询应始终指定列本身,而不是依赖于“select *”的排序。我不知道任何允许更改它们的数据库......好吧,直到你提到它,我才知道 MySQL 允许它。

        【讨论】:

        【解决方案7】:

        通常情况下,最大的因素是下一个必须在系统上工作的人。我尝试首先拥有主键列,然后是外键列,然后是其余列按重要性/对系统重要性的降序排列。

        【讨论】:

        • 我们通常从“创建”的最后一列开始(插入行的时间戳)。当然,对于较旧的表,它可以在之后添加几列......而且我们偶尔会在表中将复合主键更改为代理键,因此主键会超过几列。
        【解决方案8】:

        如果您要经常使用 UNION,如果您对列的排序有一个约定,那么匹配列会更容易。

        【讨论】:

        • 听起来您的数据库需要规范化! :)
        • 嘿!拿回去,我没说我的数据库。 :)
        • 你能 UNION 与 2 个表中的列顺序不同的顺序吗?
        • 是的,您只需要在查询表时明确指定列。对于表 A[a,b] B[b,a],这意味着 (SELECT aa, ab FROM A) UNION (SELECT ba, bb FROM B) 而不是 (SELECT * FROM A) UNION (SELECT * FROM B)。
        【解决方案9】:

        更新:

        MySQL,可能有这样做的理由。

        由于可变数据类型(如VARCHAR)以可变长度存储在InnoDB 中,因此数据库引擎应遍历每一行中的所有先前列以找出给定列的偏移量。

        20 列的影响可能高达 17%

        更多详情请参阅我的博客中的此条目:

        Oracle 中,尾随NULL 列不占用空间,这就是为什么您应该始终将它们放在表的末尾。

        同样在OracleSQL Server 中,如果行较大,可能会出现ROW CHAINING

        ROW CHANING 正在拆分一个不适合一个块的行并将其跨越多个块,并通过链表连接。

        读取不适合第一个块的尾随列将需要遍历链表,这将导致额外的I/O 操作。

        参见this page 以了解OracleROW CHAINING 的说明:

        这就是为什么你应该把你经常使用的列放在表的开头,把你不经常使用的列或者倾向于NULL的列放在表的末尾。

        重要提示:

        如果您喜欢这个答案并想为它投票,请同时投票给@Andomar's answer

        他回答了同样的问题,但似乎无缘无故被否决了。

        【讨论】:

        • 所以你说这会很慢:select tinyTable.id, tblBIG.firstColumn, tblBIG.lastColumn from tinyTable inner join tblBIG on tinyTable.id = tblBIG.fkID 如果 tblBIG 记录超过 8KB (在这种情况下会发生一些行链接)并且连接将是同步的......但这会很快:从 tinyTable 中选择 tinyTable.id, tblBIG.firstColumn 在 tinyTable.id = tblBIG.fkID 上连接 tblBIG 因为我不会在其他块中使用该列,因此无需遍历链表我做对了吗?
        • 我只得到 6%,这是 col1 与 any 其他列的比较。
        【解决方案10】:

        我能想到的唯一原因是调试和救火。我们有一个表,其“名称”列在列表中的第 10 位左右出现。当您从表中的 id in (1,2,3) 中快速选择 * 然后您必须滚动查看名称时,这会很痛苦。

        但仅此而已。

        【讨论】:

          【解决方案11】:

          通常,当您通过 Management Studio 更改列顺序时,SQL Server 中会发生什么,它会创建一个具有新结构的临时表,将数据从旧表移动到该结构,删除旧表并重命名新表一。正如您可能想象的那样,如果您有一张大桌子,这对于性能来说是一个非常糟糕的选择。我不知道 My SQL 是否也这样做,但这是我们许多人避免重新排序列的原因之一。因为 select * 永远不应该在生产系统中使用,所以在最后添加列对于设计良好的系统来说不是问题。表格中的列顺序一般不应该乱七八糟。

          【讨论】:

            【解决方案12】:

            如上所述,存在许多潜在的性能问题。我曾经在一个数据库上工作,如果您没有在查询中引用这些列,那么在最后放置非常大的列会提高性能。显然,如果一条记录跨越多个磁盘块,数据库引擎在获得所需的所有列后可能会停止读取块。

            当然,任何性能影响不仅高度依赖于您使用的制造商,还可能依赖于版本。几个月前,我注意到我们的 Postgres 无法使用索引进行“点赞”比较。也就是说,如果你写了“somecolumn like 'M%'”,那么在找到第一个 N 时跳到 M 并退出是不够聪明的。我打算将一堆查询更改为使用“between”。然后我们得到了一个新版本的 Postgres,它智能地处理了类似的事情。很高兴我从来没有改变查询。显然与此处没有直接关系,但我的观点是,您出于效率考虑所做的任何事情都可能在下一个版本中过时。

            列顺序几乎总是与我非常相关,因为我经常编写通用代码来读取数据库模式以创建屏幕。就像,我的“编辑记录”屏幕几乎总是通过读取架构以获取字段列表,然后按顺序显示它们来构建。如果我改变了列的顺序,我的程序仍然可以工作,但是显示对用户来说可能很奇怪。就像,您希望看到姓名/地址/城市/州/邮编,而不是城市/地址/邮编/姓名/州。当然,我可以将列的显示顺序放在代码或控制文件或其他东西中,但是每次添加或删除列时,我们都必须记住去更新控制文件。我喜欢说一次。此外,当编辑屏幕完全由模式构建时,添加一个新表可能意味着编写零行代码来为其创建一个编辑屏幕,这非常酷。 (好吧,好吧,实际上通常我必须在菜单中添加一个条目来调用通用编辑程序,而我通常已经放弃了通用的“选择要更新的记录”,因为有太多的例外使其实用.)

            【讨论】:

              【解决方案13】:

              除了明显的性能调整之外,我还遇到了一个极端情况,重新排序列会导致(以前的功能)sql 脚本失败。

              从文档“TIMESTAMP 和 DATETIME 列没有自动属性,除非明确指定它们,但有以下例外:默认情况下,第一个 TIMESTAMP 列同时具有 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 如果两者都没有明确指定”https://dev.mysql.com/doc/refman/5.6/en/timestamp-initialization.html

              因此,如果该字段是表中的第一个时间戳(或日期时间),则命令 ALTER TABLE table_name MODIFY field_name timestamp(6) NOT NULL; 将有效,但否则无效。

              显然,您可以更正该 alter 命令以包含默认值,但是由于列重新排序而导致有效的查询停止工作的事实让我很头疼。

              【讨论】:

                【解决方案14】:

                2002 年,Bill Thorsteinson 在 Hewlett Packard 论坛上发布了他关于通过重新排序列来优化 MySQL 查询的建议。从那以后,他的帖子在互联网上至少被复制和粘贴了一百次,而且通常没有引用。准确引用他的话……

                一般经验法则:

                • 主键列优先。
                • 接下来是外键列。
                • 接下来是经常搜索的列。
                • 以后经常更新的专栏。
                • 可空列最后。
                • 在最常使用的可空列之后,使用最少的可空列。
                • 自己的表中存在 Blob,其他列很少。

                来源:HP Forums.

                但那篇文章完全是在 2002 年发表的! 这个建议是针对 MySQL 3.23 版的,比 MySQL 5.1 发布早了六年多。并且没有参考或引用。那么,比尔是对的吗?存储引擎在这个级别上究竟是如何工作的?

                1. 是的,比尔是对的。
                2. 这一切都归结为链式行和内存块的问题。

                The Secrets of Oracle Row Chaining and Migration 的一篇文章中引用 an Oracle-certified professional 的 Martin Zahn...

                链式行对我们的影响不同。在这里,这取决于我们需要的数据。如果我们有一个包含两列的行分布在两个块中,则查询:

                SELECT column1 FROM table

                column1 在块 1 中,不会导致任何«table fetch continue row»。它实际上不必获取 column2,它不会一直跟随链接的行。另一方面,如果我们要求:

                SELECT column2 FROM table

                并且由于行链接,column2 在 Block 2 中,那么您实际上会看到一个«table fetch continue row»

                这篇文章的其余部分读起来相当不错!但我在这里只引用与我们手头的问题直接相关的部分。

                18 年多过去了,我得说:谢谢,比尔!

                【讨论】:

                  猜你喜欢
                  • 2011-01-13
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多