Commit 0c4d11e8 authored by Jan Lindström's avatar Jan Lindström

MDEV-13206: INSERT ON DUPLICATE KEY UPDATE foreign key fail

This is caused by following change:

commit 95d29c99f01882ffcc2259f62b3163f9b0e80c75
Author: Marko Mäkelä <marko.makela@oracle.com>
Date:   Tue Nov 27 11:12:13 2012 +0200

    Bug#15920445 INNODB REPORTS ER_DUP_KEY BEFORE CREATE UNIQUE INDEX COMPLETED

    There is a phase during online secondary index creation where the index has
    been internally completed inside InnoDB, but does not 'officially' exist yet.
    We used to report ER_DUP_KEY in these situations, like this:

    ERROR 23000: Can't write; duplicate key in table 't1'

    What we should do is to let the 'offending' operation complete, but report an
    error to the
    ALTER TABLE t1 ADD UNIQUE KEY (c2):

    ERROR HY000: Index c2 is corrupted
    (This misleading error message should be fixed separately:
    Bug#15920713 CREATE UNIQUE INDEX REPORTS ER_INDEX_CORRUPT INSTEAD OF DUPLICATE)

    row_ins_sec_index_entry_low(): flag the index corrupted instead of
    reporting a duplicate, in case the index has not been published yet.

    rb:1614 approved by Jimmy Yang

Problem is that after we have found duplicate key on primary key
we continue to get necessary gap locks in secondary indexes to
block concurrent transactions from inserting the searched records.
However, search from unique index used in foreign key constraint
could return DB_NO_REFERENCED_ROW if INSERT .. ON DUPLICATE KEY UPDATE
does not contain value for foreign key column. In this case
we should return the original DB_DUPLICATE_KEY error instead
of DB_NO_REFERENCED_ROW.

Consider as a example following:

create table child(a int not null primary key,
b int not null,
c int,
unique key (b),
foreign key (b) references
parent (id)) engine=innodb;

insert into child values (1,1,2);

insert into child(a) values (1) on duplicate key update c = 3;

Now primary key value 1 naturally causes duplicate key error that will be
stored on node->duplicate. If there was no duplicate key error, we should
return the actual no referenced row error. As value for column b used in
both unique key and foreign key is not provided, server uses 0 as a
search value. This is naturally, not found leading to DB_NO_REFERENCED_ROW.
But, we should update the row with primay key value 1 anyway as
requested by on duplicate key update clause.
parent 2401d14e
set sql_mode='';
set innodb_strict_mode=0;
CREATE TABLE `v` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT v values (1);
CREATE TABLE `vp` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`v_id` int(10) unsigned NOT NULL,
`p_id` int(10) unsigned NOT NULL,
`ppp` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `IDX_vp_uniq` (`v_id`,`p_id`),
KEY `FK_vp_v` (`v_id`),
CONSTRAINT `FK_vp_v` FOREIGN KEY (`v_id`) REFERENCES `v` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT vp VALUES (12, 1, 100, 'text12');
INSERT INTO `vp` (`id`,`ppp`) VALUES (12, 'test12-2') ON DUPLICATE KEY UPDATE `ppp` = VALUES(`ppp`);
Warnings:
Warning 1364 Field 'v_id' doesn't have a default value
Warning 1364 Field 'p_id' doesn't have a default value
SELECT * FROM vp;
id v_id p_id ppp
12 1 100 test12-2
DROP TABLE vp, v;
CREATE TABLE t1 (i int PRIMARY KEY) ENGINE=InnoDB;
INSERT into t1 values (1);
CREATE TABLE t2 (
i int not null primary key,
vi int not null,
m int,
UNIQUE KEY (vi),
CONSTRAINT `cc` FOREIGN KEY (vi) REFERENCES t1 (i) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT into t2 VALUES (1, 1, 100);
INSERT INTO t2 (i,m) VALUES (1, 2) ON DUPLICATE KEY UPDATE m=3;
Warnings:
Warning 1364 Field 'vi' doesn't have a default value
SELECT * FROM t2;
i vi m
1 1 3
DROP TABLE t2,t1;
CREATE TABLE t1 (i int PRIMARY KEY) ENGINE=InnoDB;
INSERT into t1 values (1);
CREATE TABLE t2 (
i int not null primary key,
vi int not null,
m int,
KEY (vi),
CONSTRAINT `cc` FOREIGN KEY (vi) REFERENCES t1 (i) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT into t2 VALUES (1, 1, 100);
INSERT INTO t2 (i,m) VALUES (1, 2) ON DUPLICATE KEY UPDATE m=3;
Warnings:
Warning 1364 Field 'vi' doesn't have a default value
SELECT * FROM t2;
i vi m
1 1 3
DROP TABLE t2, t1;
--source include/have_innodb.inc
#
# MDEV-13206: INSERT ON DUPLICATE KEY UPDATE foreign key fail
#
set sql_mode='';
set innodb_strict_mode=0;
CREATE TABLE `v` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT v values (1);
CREATE TABLE `vp` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`v_id` int(10) unsigned NOT NULL,
`p_id` int(10) unsigned NOT NULL,
`ppp` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `IDX_vp_uniq` (`v_id`,`p_id`),
KEY `FK_vp_v` (`v_id`),
CONSTRAINT `FK_vp_v` FOREIGN KEY (`v_id`) REFERENCES `v` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT vp VALUES (12, 1, 100, 'text12');
INSERT INTO `vp` (`id`,`ppp`) VALUES (12, 'test12-2') ON DUPLICATE KEY UPDATE `ppp` = VALUES(`ppp`);
SELECT * FROM vp;
DROP TABLE vp, v;
CREATE TABLE t1 (i int PRIMARY KEY) ENGINE=InnoDB;
INSERT into t1 values (1);
CREATE TABLE t2 (
i int not null primary key,
vi int not null,
m int,
UNIQUE KEY (vi),
CONSTRAINT `cc` FOREIGN KEY (vi) REFERENCES t1 (i) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT into t2 VALUES (1, 1, 100);
INSERT INTO t2 (i,m) VALUES (1, 2) ON DUPLICATE KEY UPDATE m=3;
SELECT * FROM t2;
DROP TABLE t2,t1;
CREATE TABLE t1 (i int PRIMARY KEY) ENGINE=InnoDB;
INSERT into t1 values (1);
CREATE TABLE t2 (
i int not null primary key,
vi int not null,
m int,
KEY (vi),
CONSTRAINT `cc` FOREIGN KEY (vi) REFERENCES t1 (i) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
INSERT into t2 VALUES (1, 1, 100);
INSERT INTO t2 (i,m) VALUES (1, 2) ON DUPLICATE KEY UPDATE m=3;
SELECT * FROM t2;
DROP TABLE t2, t1;
......@@ -3608,6 +3608,11 @@ row_ins(
switch (err) {
case DB_SUCCESS:
break;
case DB_NO_REFERENCED_ROW:
if (!dict_index_is_unique(node->index)) {
DBUG_RETURN(err);
}
/* fall through */
case DB_DUPLICATE_KEY:
ut_ad(dict_index_is_unique(node->index));
......@@ -3624,7 +3629,55 @@ row_ins(
secondary indexes to block concurrent
transactions from inserting the
searched records. */
if (!node->duplicate) {
if (err == DB_NO_REFERENCED_ROW
&& node->duplicate) {
/* A foreign key check on a
unique index may fail to
find the record.
Consider as a example
following:
create table child(a int not null
primary key, b int not null,
c int,
unique key (b),
foreign key (b) references
parent (id)) engine=innodb;
insert into child values
(1,1,2);
insert into child(a) values
(1) on duplicate key update
c = 3;
Now primary key value 1
naturally causes duplicate
key error that will be
stored on node->duplicate.
If there was no duplicate
key error, we should return
the actual no referenced
row error.
As value for
column b used in both unique
key and foreign key is not
provided, server uses 0 as a
search value. This is
naturally, not found leading
to DB_NO_REFERENCED_ROW.
But, we should update the
row with primay key value 1
anyway.
Return the
original DB_DUPLICATE_KEY
error after
placing all gaplocks. */
err = DB_DUPLICATE_KEY;
break;
} else if (!node->duplicate) {
/* Save 1st dup error. Ignore
subsequent dup errors. */
node->duplicate = node->index;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment