My solution. I initially wanted the table's uniqueID in a navOrder column. But the issues of getting the uniqueID of the table in question whilst in a trigger for a bulk insert was too problematic. So I built a mechanism similar to MSSQL's ROWNUMBER feature where every row is numbered, regardless of the table or schema that the row is in. Whilst the solution I've built below doesn't generate a number for every table (it could if the triggers were added to every table), it solves my problem of needing a unique value for every row.
So, I have 2 tables, each with a BEFORE INSERT trigger that calls upon a User Defined Function (UDF) that will get the next unique sequential number.
I've bulk tested the functionality (inserting 1,000 rows in 1 query, running 1,000 queries, running all that 10 times in parallel) and we use this for a site that experiences around 2,000 real time users a minute and approximately 15,000 inserts a minute. Not a Facebook, but all we use this and it is working for us.
If you run the code below, you'll see that rolling back does NOT rollback the sequential number.
We get no deadlocks and no duplicate values (as the unique constraint on the navOrder columns does not allow duplicates).
For me this is a relatively easy to understand solution.
CREATE SCHEMA TestLastInsertId;
USE TestLastInsertId;
CREATE TABLE Table1 (
`tempID` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`item` VARCHAR(256) NOT NULL,
`navOrder` INT(11) UNIQUE NOT NULL,
`createdAt` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updatedAt` TIMESTAMP(6) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(6)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci AUTO_INCREMENT = 1;
CREATE TABLE Table2 (
`tempID` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`item` VARCHAR(256) NOT NULL,
`navOrder` INT(11) UNIQUE NOT NULL,
`createdAt` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updatedAt` TIMESTAMP(6) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(6)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci AUTO_INCREMENT = 1;
CREATE TABLE IF NOT EXISTS `nav_order_sequence` (
`navOrderSequence` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
DELIMITER ;;
CREATE FUNCTION getNextNavOrder()
RETURNS INT(11) LANGUAGE SQL NOT DETERMINISTIC MODIFIES SQL DATA SQL SECURITY INVOKER
BEGIN
INSERT INTO nav_order_sequence() VALUES();
SET @navOrder = LAST_INSERT_ID();
DELETE FROM nav_order_sequence WHERE navOrderSequence = @navOrder;
RETURN @navOrder;
END;;
CREATE TRIGGER Table1_BEFORE_INSERT BEFORE INSERT ON Table1 FOR EACH ROW
BEGIN
SET NEW.navOrder = getNextNavOrder();
END;;
CREATE TRIGGER Table2_BEFORE_INSERT BEFORE INSERT ON Table2 FOR EACH ROW
BEGIN
SET NEW.navOrder = getNextNavOrder();
END;;
DELIMITER ;
INSERT INTO Table1(item) VALUES('Item1'),('Item2'),('Item3');
INSERT INTO Table2(item) VALUES('Item4'),('Item5'),('Item6');
SELECT * FROM Table1; -- Result 1
SELECT * FROM Table2; -- Result 2
BEGIN;
INSERT INTO Table1(item) VALUES('Item7'),('Item8'),('Item9');
INSERT INTO Table2(item) VALUES('Item10'),('Item11'),('Item12');
SELECT * FROM Table1; -- Result 3
SELECT * FROM Table2; -- Result 4
ROLLBACK;
INSERT INTO Table1(item) VALUES('Item13'),('Item14'),('Item15');
INSERT INTO Table2(item) VALUES('Item16'),('Item17'),('Item18');
SELECT * FROM Table1; -- Result 5
SELECT * FROM Table2; -- Result 6
DROP SCHEMA TestLastInsertId;
Result 1 - Add 3 rows to Table 1 - navOrders 1, 2, and 3.
1 Item1 1 2019-11-02 18:58:28.657690
2 Item2 2 2019-11-02 18:58:28.657690
3 Item3 3 2019-11-02 18:58:28.657690
Result 2 - Add 3 rows to Table 2 - navOrders 4, 5, and 6.
1 Item4 4 2019-11-02 18:58:28.669873
2 Item5 5 2019-11-02 18:58:28.669873
3 Item6 6 2019-11-02 18:58:28.669873
Result 3 - Add 3 more rows to Table 1 - navOrders 7, 8, and 9.
1 Item1 1 2019-11-02 18:58:28.657690
2 Item2 2 2019-11-02 18:58:28.657690
3 Item3 3 2019-11-02 18:58:28.657690
4 Item7 7 2019-11-02 18:58:28.704766
5 Item8 8 2019-11-02 18:58:28.704766
6 Item9 9 2019-11-02 18:58:28.704766
Result 4 - Add 3 more rows to Table 2 - navOrders 10, 11, and 12.
1 Item4 4 2019-11-02 18:58:28.669873
2 Item5 5 2019-11-02 18:58:28.669873
3 Item6 6 2019-11-02 18:58:28.669873
4 Item10 10 2019-11-02 18:58:28.706930
5 Item11 11 2019-11-02 18:58:28.706930
6 Item12 12 2019-11-02 18:58:28.706930
A rollback happened here, so rows 4, 5, and 6 of both tables are removed.
Result 5 - Add 3 more rows to Table 1 after a rollback - navOrders 13, 14, and 15.
1 Item1 1 2019-11-02 18:58:28.657690
2 Item2 2 2019-11-02 18:58:28.657690
3 Item3 3 2019-11-02 18:58:28.657690
7 Item13 13 2019-11-02 18:58:28.727303
8 Item14 14 2019-11-02 18:58:28.727303
9 Item15 15 2019-11-02 18:58:28.727303
Result 6 - Add 3 more rows to Table 1 after a rollback - navOrders 16, 17, and 18.
1 Item4 4 2019-11-02 18:58:28.669873
2 Item5 5 2019-11-02 18:58:28.669873
3 Item6 6 2019-11-02 18:58:28.669873
7 Item16 16 2019-11-02 18:58:28.730307
8 Item17 17 2019-11-02 18:58:28.730307
9 Item18 18 2019-11-02 18:58:28.730307
If you were to remove the UNIQUE
constraint on navOrder and replace the function called in the triggers to be LAST_INSERT_ID()
, you'll see the duplicate values.