Bug #74659 | Foreign key constraint ignored when index is removed | ||
---|---|---|---|
Submitted: | 2 Nov 2014 10:51 | Modified: | 12 Oct 2015 5:34 |
Reporter: | Daniël van Eeden (OCA) | Email Updates: | |
Status: | Duplicate | Impact on me: | |
Category: | MySQL Server: InnoDB storage engine | Severity: | S3 (Non-critical) |
Version: | 5.6.21, 5.6.22, 5.5.41 | OS: | Any |
Assigned to: | CPU Architecture: | Any | |
Tags: | foreign key, INDEX, innodb |
[2 Nov 2014 10:51]
Daniël van Eeden
[3 Nov 2014 7:14]
MySQL Verification Team
Hello Daniël, Thank you for the bug report and test case. Confirmed that 5.6.21/5.6.22 affected. Thanks, Umesh
[3 Nov 2014 7:14]
MySQL Verification Team
// 5.6.21/5.6.22 mysql> select @@session.foreign_key_checks; +------------------------------+ | @@session.foreign_key_checks | +------------------------------+ | 1 | +------------------------------+ 1 row in set (0.00 sec) mysql> create table t1 (id int not null primary key, name varchar(100) not null); Query OK, 0 rows affected (0.02 sec) mysql> create table t2 (id int not null primary key, fid int not null, name varchar(100) not null, CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`)); Query OK, 0 rows affected (0.02 sec) mysql> insert into t1 values(1, 'test 1'),(2, 'test 2'),(3,'test 3'); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> insert into t2 values(1, 1, 'test 1'),(2, 1, 'test 2'),(3, 2, 'test 3'); Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> show create table t1\Gshow create table t2\Gselect * from t1; select * from t2; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.02 sec) *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `id` int(11) NOT NULL, `fid` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `fk_fid` (`fid`), CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) +----+--------+ | id | name | +----+--------+ | 1 | test 1 | | 2 | test 2 | | 3 | test 3 | +----+--------+ 3 rows in set (0.00 sec) +----+-----+--------+ | id | fid | name | +----+-----+--------+ | 1 | 1 | test 1 | | 2 | 1 | test 2 | | 3 | 2 | test 3 | +----+-----+--------+ 3 rows in set (0.00 sec) mysql> set foreign_key_checks=off; Query OK, 0 rows affected (0.00 sec) mysql> alter table t2 drop index `fk_fid`; Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> set foreign_key_checks=on; Query OK, 0 rows affected (0.00 sec) mysql> insert into t2 values(4, 99, 'test 1'); Query OK, 1 row affected (0.01 sec) mysql> show create table t1\Gshow create table t2\Gselect * from t1; select * from t2; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `id` int(11) NOT NULL, `fid` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) +----+--------+ | id | name | +----+--------+ | 1 | test 1 | | 2 | test 2 | | 3 | test 3 | +----+--------+ 3 rows in set (0.00 sec) +----+-----+--------+ | id | fid | name | +----+-----+--------+ | 1 | 1 | test 1 | | 2 | 1 | test 2 | | 3 | 2 | test 3 | | 4 | 99 | test 1 | +----+-----+--------+ 4 rows in set (0.00 sec) mysql> \q Bye [root@cluster-repo mysql-advanced-5.6.22]# ps aux|grep mysqld root 25886 0.3 9.8 3413872 600648 pts/0 Sl 13:55 0:01 /data/ushastry/server/mysql-advanced-5.6.22/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-advanced-5.6.22 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root root 25951 0.0 0.0 103256 832 pts/0 R+ 14:02 0:00 grep mysqld [root@cluster-repo mysql-advanced-5.6.22]# kill -9 25886 [root@cluster-repo mysql-advanced-5.6.22]# [1]+ Killed /data/ushastry/server/mysql-advanced-5.6.22/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-advanced-5.6.22 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root 2>&1 [root@cluster-repo mysql-advanced-5.6.22]# /data/ushastry/server/mysql-advanced-5.6.22/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-advanced-5.6.22 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root 2>&1 & [1] 25952 [root@cluster-repo mysql-advanced-5.6.22]# [root@cluster-repo mysql-advanced-5.6.22]# [root@cluster-repo mysql-advanced-5.6.22]# 2014-11-05 14:03:08 0 [Warning] Using unique option prefix core instead of core-file is deprecated and will be removed in a future release. Please use the full name instead. 2014-11-05 14:03:08 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). [root@cluster-repo mysql-advanced-5.6.22]# bin/mysql -u root -p test Enter password: Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 5.6.22-enterprise-commercial-advanced MySQL Enterprise Server - Advanced Edition (Commercial) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show tables; +----------------+ | Tables_in_test | +----------------+ | t1 | | t2 | +----------------+ 2 rows in set (0.00 sec) mysql> select * from t2; ERROR 1146 (42S02): Table 'test.t2' doesn't exist
[3 Nov 2014 7:15]
MySQL Verification Team
// 5.7.6 - Doesn't allow to drop index mysql> show variables like '%version%'; +-------------------------+---------------------------------------------------------+ | Variable_name | Value | +-------------------------+---------------------------------------------------------+ | innodb_version | 5.7.6 | | protocol_version | 10 | | slave_type_conversions | | | version | 5.7.6-m16-enterprise-commercial-advanced | | version_comment | MySQL Enterprise Server - Advanced Edition (Commercial) | | version_compile_machine | x86_64 | | version_compile_os | Linux | +-------------------------+---------------------------------------------------------+ 7 rows in set (0.00 sec) mysql> show variables like '%sql_mode%'; +---------------+--------------------------------------------+ | Variable_name | Value | +---------------+--------------------------------------------+ | sql_mode | STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION | +---------------+--------------------------------------------+ 1 row in set (0.00 sec) mysql> select @@session.foreign_key_checks; +------------------------------+ | @@session.foreign_key_checks | +------------------------------+ | 1 | +------------------------------+ 1 row in set (0.00 sec) mysql> create table t1 (id int not null primary key, name varchar(100) not null); Query OK, 0 rows affected (0.03 sec) mysql> create table t2 (id int not null primary key, fid int not null, name varchar(100) not null, CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`)); Query OK, 0 rows affected (0.03 sec) mysql> insert into t1 values(1, 'test 1'),(2, 'test 2'),(3,'test 3'); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> insert into t2 values(1, 1, 'test 1'),(2, 1, 'test 2'),(3, 2, 'test 3'); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> show create table t1\Gshow create table t2\Gselect * from t1; select * from t2; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `id` int(11) NOT NULL, `fid` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `fk_fid` (`fid`), CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) +----+--------+ | id | name | +----+--------+ | 1 | test 1 | | 2 | test 2 | | 3 | test 3 | +----+--------+ 3 rows in set (0.01 sec) +----+-----+--------+ | id | fid | name | +----+-----+--------+ | 1 | 1 | test 1 | | 2 | 1 | test 2 | | 3 | 2 | test 3 | +----+-----+--------+ 3 rows in set (0.00 sec) mysql> set foreign_key_checks=off; Query OK, 0 rows affected (0.00 sec) mysql> alter table t2 drop index `fk_fid`; ERROR 1553 (HY000): Cannot drop index 'fk_fid': needed in a foreign key constraint mysql>
[3 Nov 2014 7:24]
MySQL Verification Team
// 5.5.41 mysql> show variables like '%version%'; +-------------------------+------------------------------+ | Variable_name | Value | +-------------------------+------------------------------+ | innodb_version | 5.5.41 | | protocol_version | 10 | | slave_type_conversions | | | version | 5.5.41 | | version_comment | MySQL Community Server (GPL) | | version_compile_machine | x86_64 | | version_compile_os | Linux | +-------------------------+------------------------------+ 7 rows in set (0.00 sec) mysql> select @@session.foreign_key_checks; +------------------------------+ | @@session.foreign_key_checks | +------------------------------+ | 1 | +------------------------------+ 1 row in set (0.00 sec) mysql> create table t1 (id int not null primary key, name varchar(100) not null); Query OK, 0 rows affected (0.01 sec) mysql> create table t2 (id int not null primary key, fid int not null, name varchar(100) not null, CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`)); Query OK, 0 rows affected (0.01 sec) mysql> insert into t1 values(1, 'test 1'),(2, 'test 2'),(3,'test 3'); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> insert into t2 values(1, 1, 'test 1'),(2, 1, 'test 2'),(3, 2, 'test 3'); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> show create table t1\Gshow create table t2\Gselect * from t1; select * from t2; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.02 sec) *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `id` int(11) NOT NULL, `fid` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `fk_fid` (`fid`), CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) +----+--------+ | id | name | +----+--------+ | 1 | test 1 | | 2 | test 2 | | 3 | test 3 | +----+--------+ 3 rows in set (0.00 sec) +----+-----+--------+ | id | fid | name | +----+-----+--------+ | 1 | 1 | test 1 | | 2 | 1 | test 2 | | 3 | 2 | test 3 | +----+-----+--------+ 3 rows in set (0.00 sec) mysql> set foreign_key_checks=off; Query OK, 0 rows affected (0.00 sec) mysql> alter table t2 drop index `fk_fid`; Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> set foreign_key_checks=on; Query OK, 0 rows affected (0.00 sec) mysql> insert into t2 values(4, 99, 'test 1'); Query OK, 1 row affected (0.00 sec) mysql> show create table t1\Gshow create table t2\Gselect * from t1; select * from t2; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `id` int(11) NOT NULL, `fid` int(11) NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk_fid` FOREIGN KEY (`fid`) REFERENCES `t1` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) +----+--------+ | id | name | +----+--------+ | 1 | test 1 | | 2 | test 2 | | 3 | test 3 | +----+--------+ 3 rows in set (0.00 sec) +----+-----+--------+ | id | fid | name | +----+-----+--------+ | 1 | 1 | test 1 | | 2 | 1 | test 2 | | 3 | 2 | test 3 | | 4 | 99 | test 1 | +----+-----+--------+ 4 rows in set (0.00 sec) mysql> \q Bye [root@cluster-repo mysql-5.5.41]# ps aux|grep mysqld root 26148 0.4 3.4 2649588 212704 pts/0 Sl 14:26 0:00 /data/ushastry/server/mysql-5.5.41/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-5.5.41 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root root 26174 0.0 0.0 103256 828 pts/0 S+ 14:29 0:00 grep mysqld [root@cluster-repo mysql-5.5.41]# kill -9 26148 [root@cluster-repo mysql-5.5.41]# /data/ushastry/server/mysql-5.5.41/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-5.5.41 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root 2>&1 & [2] 26176 [1] Killed /data/ushastry/server/mysql-5.5.41/bin/mysqld --innodb_buffer_pool_size=2147483648 --basedir=/data/ushastry/server/mysql-5.5.41 --datadir=/tmp/bug --core --socket=/tmp/mysql.sock --port=3306 --log-error=/tmp/bug/log.err --user=root 2>&1 [root@cluster-repo mysql-5.5.41]# 141105 14:29:14 [Warning] Using unique option prefix core instead of core-file is deprecated and will be removed in a future release. Please use the full name instead. [root@cluster-repo mysql-5.5.41]# bin/mysql -u root -p test Enter password: Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 5.5.41 MySQL Community Server (GPL) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select * from t2; ERROR 1146 (42S02): Table 'test.t2' doesn't exist
[3 Nov 2014 8:04]
Daniël van Eeden
PostgreSQL 9.4 does not create the index on the child table and correctly enforces the constraint. The same is true for SQLite3. fktest=# create table t1 (id int not null primary key, name varchar(100) not null); CREATE TABLE fktest=# create table t2 (id int not null primary key, fid int not null, name varchar(100) not null, CONSTRAINT fk_fid FOREIGN KEY (fid) REFERENCES t1 (id)); CREATE TABLE fktest=# \d t1 Table "public.t1" Column | Type | Modifiers --------+------------------------+----------- id | integer | not null name | character varying(100) | not null Indexes: "t1_pkey" PRIMARY KEY, btree (id) Referenced by: TABLE "t2" CONSTRAINT "fk_fid" FOREIGN KEY (fid) REFERENCES t1(id) fktest=# \d t1 Table "public.t1" Column | Type | Modifiers --------+------------------------+----------- id | integer | not null name | character varying(100) | not null Indexes: "t1_pkey" PRIMARY KEY, btree (id) Referenced by: TABLE "t2" CONSTRAINT "fk_fid" FOREIGN KEY (fid) REFERENCES t1(id) fktest=# insert into t1 values (1,'test'); INSERT 0 1 fktest=# insert into t2 values (1, 99, 'test'); ERROR: insert or update on table "t2" violates foreign key constraint "fk_fid" DETAIL: Key (fid)=(99) is not present in table "t1". $ sqlite3 /tmp/foo SQLite version 3.8.6 2014-08-15 11:46:33 Enter ".help" for usage hints. sqlite> create table t1 (id int not null primary key, name varchar(100) not null); sqlite> create table t2 (id int not null primary key, fid int not null, name varchar(100) not null, CONSTRAINT fk_fid FOREIGN KEY (fid) REFERENCES t1 (id)); sqlite> .indices t1 sqlite_autoindex_t1_1 sqlite> .indices t2 sqlite_autoindex_t2_1 sqlite> insert into t1 values (1,'test'); sqlite> insert into t2 values (1, 1,'test'); sqlite> insert into t2 values (1, 99,'test'); Error: UNIQUE constraint failed: t2.id sqlite>
[10 Nov 2014 10:59]
Marko Mäkelä
Posted by developer: I implemented ALTER TABLE...DROP FOREIGN KEY and ALTER TABLE...DROP INDEX for ALGORITHM=INPLACE in MySQL 5.6, in WL#5545 and WL#5526. Jimmy Yang implemented ALTER TABLE...ADD FOREIGN KEY...ALGORITHM=INPLACE in WL#6251 (when foreign_key_checks=off). That said, this seems to be a design problem in all InnoDB versions. The foreign key checks are implemented by row_ins_foreign_check_on_constraint() and row_upd_check_references_constraints(). If you drop the 'backing index' from the child table, then it seems that the check will be skipped, by design. In InnoDB, a DML operation is implemented like this: 1. Write undo log record. 2. Check lock on the clustered index. 3. Attempt to modify the clustered index. 4. For each secondary index: 4a. Check lock on the secondary index. 4b. Attempt to modify the secondary index. The foreign key check would normally be performed at step 4b, and a failure would lead to a partial rollback, going back to undo log as it was before step 1. As far as I can tell, rollback, purge and MVCC are unaffected by this bug. In a cascaded operation, step 3 or step 4b would generate undo log on modifying parent or child tables. I guess that PostgreSQL and SQLite are performing table scans in order to satisfy foreign key constraints when there is no underlying index. The current design of InnoDB does not allow that. I do not think that we can change this behaviour in MySQL 5.7 or earlier. In current versions of MySQL (up to version 5.7), it should be doable to set a flag when some underlying index for a FOREIGN KEY constraint is missing. When the flag is set, we should refuse any operation that would generate undo log.
[12 Oct 2015 5:34]
ADITYA ANANTHAPADMANABHA
Duplicate of http://bugs.mysql.com/bug.php?id=70260