37

I am trying to replace an InnoDB table with a new table, and I want all foreign key references that point to the old table to point to the new table.

So I tried this:

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

Unfortunately, even with foreign_key_checks disabled, all references pointing to foo are changed to point to foo_old. Now I am looking for either

  • a way to change the foreign key references back without rebuilding the entire table, OR
  • a way to rename a table without updating foreign key references.

I tried dropping the foreign keys and recreating them, but since the tables are huge, it takes hours. The whole point of replacing the table was to make a schema change with limited downtime.

Bart van Wissen
  • 391
  • 1
  • 3
  • 4
  • Does anyone know if there are any updates on this issue in 2015? – Greg Nov 06 '15 at 14:07
  • 1
    Is the replacement table identical, or slightly modified? For example, could the change be done by an alter? If so, you may want to look at [pt-online-schema-change --alter-foreign-keys-change](https://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--alter-foreign-keys-method). – Dave Nov 10 '15 at 13:06

5 Answers5

13

Old questions, but following is a possible way around. Basically move the data rather than renaming the tables. You need to of course make sure the new data adhere to the foreign key rules.

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;

Make sure you run it as one query so the foreign_key_checks applies to the whole thing. Hope this helps.

xelber
  • 4,197
  • 3
  • 25
  • 33
  • 8
    This is unfeasible if the table has millions of records and the time to do it is short. (Think two hours to the first copy, some time to truncate and two more hours to copy back.) – Leonel Martins Mar 20 '14 at 18:30
  • Like wont create the foreign keys.. you need to check that? – Adarsh Gangadharan Apr 06 '18 at 06:49
  • It's ridiculous that you cannot simply rename a table without introducing potentially hours and hours of data operations. Whatever database engine requires this sucks. I use SQL Server and renaming a table is easy. – Triynko Jun 07 '19 at 17:02
  • 1
    @Triynko SQL Server has the same behaviour as MySQL when renaming a table referenced by a foreign key, the renaming is instantaneous but the renamed table is still referenced by FKs. I'm not sure you fully read the original issue – Alfred May 12 '20 at 10:06
11

Unfortunately I don't think that there's a way around your problem without dropping the foreign keys first and they re-creating them.

This is minor but I spotted something with your RENAME commands too. You can chain them together and unless all steps were successful it rolls back all of the other renames. Here's the syntax:

RENAME TABLE foo TO foo_old, foo_new TO foo;
James C
  • 14,047
  • 1
  • 34
  • 43
  • 5
    I noticed that dropping foo and then recreating it keeps the foreign keys intact. Since foo_new is basically a copy (plus extras) of foo (creating using SELECT INTO OUTFILE and then LOAD DATA INFILE), this could be a solution. I would just drop the table, recreate it and then load the data into it. Only with the renaming, I like the fact that I have a 'backup' table for when something goes wrong. And it seems backward that dropping a table leaves the references untouched (even though they then point to a nonexistent table) but renaming a table is not possible without changing the references. – Bart van Wissen Jun 17 '11 at 11:29
  • @BartvanWissen, did this approach end up working for you? – Greg Nov 06 '15 at 14:07
  • 2
    @JamesC, `RENAME` will change the foreign keys for you. i.e. if `bar` references `foo` and you rename `foo` to `foo_old`, all foreign keys on `bar` will now reference `foo_old`. Chaining renames to single statement doesn't change this. – vhu Nov 06 '15 at 18:58
8

On MySQL 5.6 with innodb_file_per_table=ON allows you to swap the table spaces on the fly. This can't be done completely using SQL as file operations need to be performed separately. First prepare the foo_new table to be copied and drop the foo data:

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

At this point you need to copy the relevant InnoDB files to correct name. Files are stored in your data directory. On Debian, for example, they are by default in /var/lib/mysql/yourdatabase and files are foo_new.ibd, foo_new.cfg and foo_new.frm. Copy them to foo.ibd, foo.cfg and foo.frm, respectively. For example:

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

Pay attention that MySQL has access to the new files (e.g. they have correct owner, access rights). Once done you can import the table again and enable foreign keys:

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

This only copies foo_new to foo. Repeat the steps if you need to copy foo to foo_old.

vhu
  • 12,244
  • 11
  • 38
  • 48
5

InnoDB uses the internal pointer of the table in the foreign key, so no matter the name given to this table (using RENAME), constraints will be preserved including when you use SET foreign_key_checks = 0.

a way to change the foreign key references back without rebuilding the entire table

Using innodb_file_per_table=ON would be the nearest we can go (see @vhu answer).

a way to rename a table without updating foreign key references.

The solution which will imply the lowest downtime and effort, and does not require shell access to the server, might simply be to work with two databases, and switch them on due time, if the data does not change very much

It could be even faster to synchronize every other table than the big one, or temporary duplicate mysql command (delete, update, insert) in your application until the switch if you do have some changes.

Adam
  • 17,838
  • 32
  • 54
4

I found a way around this... you just drop the source table instead of renaming it.

For this example, we'll call the table 'mytbl'.

  1. create copy of the source table, e.g. 'mytbl_new'
  2. copy data into the new table
  3. drop the source table 'mytbl'
  4. rename 'mytbl_new' to 'mytbl'

The only downside is you can't keep a backup of your original table, but you could mysqldump it before hand. Alternatively, you could create an additional table copy if you wanted a verbatim copy of the original table.

Jim Backus
  • 41
  • 2
  • db won't allow you to drop table if there are FK references to it – Ravi Sep 17 '18 at 23:14
  • this does work, at least with mariadb 10.0.36. WRT @Ravi's point, yes, you need to `SET foreign_key_checks = 0;` first. Though you do lose the atomic rename, and will have the table locked during the drop table. – ysth Nov 28 '18 at 06:02