【问题标题】:Renaming an InnoDB table without updating foreign key references to it?重命名 InnoDB 表而不更新对其的外键引用?
【发布时间】:2011-09-17 02:32:56
【问题描述】:

我正在尝试用新表替换 InnoDB 表,并且我希望所有指向旧表的外键引用都指向新表。

所以我尝试了这个:

SET foreign_key_checks = 0;
ALTER TABLE foo RENAME foo_old;
ALTER TABLE foo_new RENAME foo;

不幸的是,即使禁用了 foreign_key_checks,所有指向 foo 的引用都会更改为指向 foo_old。现在我正在寻找任何一个

  • 一种无需重建整个表即可更改外键引用的方法,或者
  • 一种在不更新外键引用的情况下重命名表的方法。

我尝试删除外键并重新创建它们,但由于表很大,因此需要几个小时。替换表的全部目的是在有限的停机时间内进行模式更改。

【问题讨论】:

标签: mysql innodb


【解决方案1】:

老问题,但以下是一种可能的解决方法。 基本上是移动数据而不是重命名表。您当然需要确保新数据符合外键规则。

SET foreign_key_checks = 0;
CREATE TABLE IF NOT EXISTS foo_old LIKE foo;
INSERT INTO foo_old SELECT * FROM foo;
TRUNCATE foo;
INSERT INTO foo SELECT * FROM foo_new;

确保将其作为一个查询运行,以便 foreign_key_checks 应用于整个事情。希望这会有所帮助。

【讨论】:

  • 如果表有数百万条记录并且时间很短,这是不可行的。 (想想第一个副本需要两个小时,截断一些时间,然后再复制两个小时。)
  • 好像不会创建外键..你需要检查吗?
  • 您不能简单地重命名表而不引入潜在的数小时和数小时的数据操作,这很荒谬。无论数据库引擎需要什么,这都很糟糕。我使用 SQL Server,重命名表很容易。
  • @Triynko SQL Server 在重命名由外键引用的表时具有与 MySQL 相同的行为,重命名是即时的,但重命名的表仍被 FK 引用。我不确定您是否完全阅读了原始问题
【解决方案2】:

不幸的是,如果不先删除外键并重新创建它们,我认为没有办法解决您的问题。

这是次要的,但我也发现了你的 RENAME 命令。您可以将它们链接在一起,除非所有步骤都成功,否则它会回滚所有其他重命名。语法如下:

RENAME TABLE foo TO foo_old, foo_new TO foo;

【讨论】:

  • 我注意到删除 foo 然后重新创建它可以保持外键不变。由于 foo_new 基本上是 foo 的副本(加上附加功能)(使用 SELECT INTO OUTFILE 然后 LOAD DATA INFILE 创建),这可能是一个解决方案。我只是删除表,重新创建它,然后将数据加载到其中。只有通过重命名,我喜欢这样一个事实,即当出现问题时我有一个“备份”表。似乎倒退了,删除一个表会使引用保持不变(即使它们指向一个不存在的表),但是如果不更改引用就不可能重命名一个表。
  • @BartvanWissen,这种方法最终对你有用吗?
  • @JamesC, RENAME 将为您更改外键。即,如果bar 引用foo 并且您将foo 重命名为foo_old,则bar 上的所有外键现在都将引用foo_old。将重命名链接到单个语句不会改变这一点。
【解决方案3】:

在 MySQL 5.6 上,innodb_file_per_table=ON 允许您动态交换表空间。这不能完全使用 SQL 完成,因为文件操作需要单独执行。首先准备要复制的foo_new表并删除foo数据:

SET foreign_key_checks = 0;
ALTER TABLE foo DISCARD TABLESPACE;
FLUSH TABLES foo_new FOR EXPORT;

此时您需要将相关的 InnoDB 文件复制到正确的名称。文件存储在您的数据目录中。例如,在 Debian 上,它们默认位于 /var/lib/mysql/yourdatabase 中,文件为 foo_new.ibdfoo_new.cfgfoo_new.frm。将它们分别复制到foo.ibdfoo.cfgfoo.frm。例如:

$ cp foo_new.ibd foo.ibd
$ cp foo_new.frm foo.frm
$ cp foo_new.cfg foo.cfg

注意 MySQL 可以访问新文件(例如,它们具有正确的所有者、访问权限)。完成后,您可以再次导入表并启用外键:

UNLOCK TABLES;
ALTER TABLE foo IMPORT TABLESPACE;
SET foreign_key_checks = 1;

这只会将foo_new 复制到foo。如果您需要将foo 复制到foo_old,请重复这些步骤。

【讨论】:

    【解决方案4】:

    InnoDB 在外键中使用表的内部指针,因此无论给该表命名(使用RENAME),约束都将被保留,包括使用SET foreign_key_checks = 0 时。

    一种无需重建整个表即可更改外键引用的方法

    使用innodb_file_per_table=ON 将是我们可以去的最近的地方(见@vhu 答案)。

    一种在不更新外键引用的情况下重命名表的方法。

    将意味着最少的停机时间和工作量,并且不需要对服务器的 shell 访问的解决方案可能只是使用两个数据库,并在适当的时候将它们打开,如果数据变化不大

    同步其他表比同步大表更快,或者在您的应用程序中临时重复 mysql 命令(删除、更新、插入)直到切换(如果您确实有一些更改)。

    【讨论】:

      【解决方案5】:

      我找到了解决这个问题的方法...您只需删除源表而不是重命名它。

      在本例中,我们将表称为“mytbl”。

      1. 创建源表的副本,例如'mytbl_new'
      2. 将数据复制到新表中
      3. 删除源表“mytbl”
      4. 将“mytbl_new”重命名为“mytbl”

      唯一的缺点是您无法保留原始表的备份,但您可以事先 mysqldump 它。或者,如果您想要原始表格的逐字副本,您可以创建一个额外的表格副本。

      【讨论】:

      • 如果有 FK 引用,db 将不允许您删除表
      • 这确实有效,至少在 mariadb 10.0.36 中是这样。 WRT @Ravi 的观点,是的,你需要先SET foreign_key_checks = 0;。尽管您确实丢失了原子重命名,并且会在删除表期间锁定该表。
      猜你喜欢
      • 1970-01-01
      • 2020-02-16
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 2015-05-12
      • 2012-08-22
      • 1970-01-01
      相关资源
      最近更新 更多