Question

Server: MariaDB 10.3.21
Client: MariaDB 10.4.12

Given the following structure/data:

DROP TABLE IF EXISTS `main`;
CREATE TABLE `main` (
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `description` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

LOCK TABLES `main` WRITE;
INSERT INTO `main` VALUES ('bar','this is another test');
INSERT INTO `main` VALUES ('foo','this is a test');
UNLOCK TABLES;

DROP TABLE IF EXISTS `referenceData`;
CREATE TABLE `referenceData` (
  `primaryName` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `secondaryName` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`primaryName`),
  KEY `referenceData_FK_1` (`secondaryName`),
  CONSTRAINT `referenceData_FK` FOREIGN KEY (`primaryName`) REFERENCES `main` (`name`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `referenceData_FK_1` FOREIGN KEY (`secondaryName`) REFERENCES `main` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

LOCK TABLES `referenceData` WRITE;
INSERT INTO `referenceData` VALUES ('bar','bar');
INSERT INTO `referenceData` VALUES ('foo','bar');
UNLOCK TABLES;

One can update/delete row 1 in table main just fine and the CASCADE performs as expected on table referenceData. However, when one attempts to update row 2 in table main (e.g. via UPDATE main SET name = 'bar2' WHERE name = 'bar'), the following error is returned:

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`testdb`.`referenceData`, CONSTRAINT `referenceData_FK_1` FOREIGN KEY (`secondaryName`) REFERENCES `main` (`name`) ON DELETE CASCADE ON UPDATE CASCADE)

If one attempts a delete via DELETE FROM main WHERE name = 'bar', however, it works fine.

What, quite frankly, in tarnation? Why does this error occur when both columns are the same value during an update, and why isn't a delete affected?

Was it helpful?

Solution

DROP TABLE IF EXISTS `main`;
CREATE TABLE IF NOT EXISTS `main` (
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `description` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

LOCK TABLES `main` WRITE;
INSERT INTO `main` VALUES ('bar','this is another test');
INSERT INTO `main` VALUES ('foo','this is a test');
UNLOCK TABLES;

DROP TABLE IF EXISTS `referenceData`;
CREATE TABLE IF NOT EXISTS `referenceData` (
  `primaryName` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `secondaryName` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`primaryName`, `secondaryName`),
  CONSTRAINT `referenceData_FK_primary` FOREIGN KEY (`primaryName`) REFERENCES `main` (`name`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `referenceData_FK_secondary` FOREIGN KEY (`secondaryName`) REFERENCES `main` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

LOCK TABLES `referenceData` WRITE;
INSERT INTO `referenceData` VALUES ('bar','bar');
INSERT INTO `referenceData` VALUES ('foo','bar');
UNLOCK TABLES;
SELECT * FROM main;
SELECT * FROM referenceData;
name | description         
:--- | :-------------------
bar  | this is another test
foo  | this is a test      

primaryName | secondaryName
:---------- | :------------
bar         | bar          
foo         | bar          

db<>fiddle here

Only referenceData DDL was edited - look.


when one attempts to update row 2 in table main (e.g. via

UPDATE main SET name = 'bar2' WHERE name = 'bar')

, the following error is returned:

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (testdb.referenceData, CONSTRAINT referenceData_FK_1 FOREIGN KEY (secondaryName) REFERENCES main (name) ON DELETE CASCADE ON UPDATE CASCADE) If one attempts a delete via DELETE FROM main WHERE name = 'bar', however, it works fine.

The reason of this issue is a record where both primaryName and secondaryName have the same value. Cascade update is performed not "by record" but "by reference" - i.e. 2 separate cascade operations attempts will be executed. And when the first cascade operation is performed (by one field) the check for another field fails (main table has one value whereas edited record needs in 2 different values - both old and new - at the same time). And I do not see the way to solve this issue (except use insert into main - update referenceData - remove from main)...

OTHER TIPS

Your problem is that the constraints are enforcing that the value in the "parent" and "child" records must be the same at all times.

Trying to update the "parent" value will fail because there are "child" values that still have the old value.

Trying to update the "child" values will fail because they're different from the "parent" value.

The only [sane] way to to do this is:

  • Insert a new "parent" record with the new value,
  • Update all the "child" records to the new value, and then
  • Delete the old, "parent" value.

Yes, that's a lot of work, which is why others have, quite rightly, said that Primary Key values should be chosen so that they do not change.

Here, you're probably working with a few records. Imagine the impact on your database server if you wanted to make this sort of change on millions of records ... Your DBMS might well take you off its Christmas Card List.

LOCK TABLES referenceData WRITE;

. . .

UNLOCK TABLES;

MySQL is plenty clever enough to handle multi-user change concurrency without the potentially huge Application performance bottlenecks caused by locking entire tables when you really don't need to.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top