MySQL InnoDB: Duplicate Key Entries nach Archivierung vermeiden
MySQL-InnoDB: Duplicate Key Entries nach Archivierung vermeiden
Nach einem Server-Neustart vergisst MySQL in Verbindung mit InnoDB die letzte auto_increment
ID. Dies kann insbesondere zu Problemen führen, wenn Tabellen archiviert und dann gelöscht werden: Die IDs beginnen wieder bei 1 und bei der nächsten Archivierung kollidieren diese IDs mit bereits archivierten IDs.
Im Folgenden wird ein Workaround beschrieben, getestet mit MySQL 5.1.54 (ubuntu), sowie 5.0.51 (SuSE):
-- Tabelle anlegen
drop table if exists tbl;
create table tbl (
id int(10) auto_increment,
primary key(id)
) type=innodb;
-- Archiv-Tabelle anlegen
drop table if exists tbl_archiv;
create table tbl_archiv (
id int(10) auto_increment,
primary key(id)
) type=innodb;
-- Datensätze einfügen
insert into tbl values (default), (default);
select * from tbl;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
-- archivieren
insert into tbl_archiv select * from tbl;
delete from tbl; -- => tbl ist jetzt leer!
Server neustarten mit sudo service mysql restart
=> Auto_increment
von tbl
beginnt jetzt bei 1 statt bei 3!
insert into tbl values (default);
select * from tbl;
+----+
| id |
+----+
| 1 |
+----+
Dies ist dokumentiertes Verhalten und wird nicht als Bug angesehen.
-- erneut archivieren
insert into tbl_archiv select * from tbl;
Folge: Es erscheint ein Duplicate Key Error, da die IDs aus tbl bereits in tbl_archiv
existieren:
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
Workaround: Mit Trigger sicherstellen, dass tbl
auch nach Serverneustart mindestens beim höchsten auto_increment von tbl_archiv
beginnt:
delimiter //
drop trigger if exists trigger_autoinc_tbl;
CREATE TRIGGER trigger_autoinc_tbl BEFORE INSERT ON tbl
FOR EACH ROW
BEGIN
declare auto_incr1 BIGINT;
declare auto_incr2 BIGINT;
IF (NEW.id=0) THEN
SELECT AUTO_INCREMENT INTO auto_incr1 FROM information_schema.TABLES WHERE table_schema=DATABASE() AND table_name='tbl';
SELECT AUTO_INCREMENT INTO auto_incr2 FROM information_schema.TABLES WHERE table_schema=DATABASE() AND table_name='tbl_archiv';
IF (auto_incr2 > auto_incr1) THEN
SET NEW.id = last_insert_id(auto_incr2);
END IF;
END IF;
END;//
delimiter ;
NEW.id
ist gleich 0, wenn default
oder null
im INSERT
-Statement übergeben wurden, da das Attribut mit NOT NULL
definiert wurde.
Die Funktion last_insert_id(auto_incr2)
liefert auto_incr2
und stellt zugleich sicher, dass beim nächsten Aufruf von last_insert_id()
ebenfalls auto_incr2
geliefert wird. UPDATE: Das klappt nicht, da mysql LAST_INSERT_ID()
wieder auf den alten Wert vor Ausführung des Triggers zurücksetzt. LAST_INSERT_ID()
liefert also falsche Werte!
Nochmal versuchen, diesmal setzen wir sogar das auto_increment
von tbl_archiv
zusätzlich hoch (dies ist tbl
zunächst nicht bekannt!):
insert into tbl_archiv values (default),(default),(default),(default);
select * from tbl_archiv;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
+----+
delete from tbl;
insert into tbl values (default);
select * from tbl;
+----+
| id |
+----+
| 7 |
+----+
Ergebnis: Das auto_increment
macht jetzt bei der nächsten ID von tbl_archiv
weiter.
-- archivieren
insert into tbl_archiv select * from tbl;
delete from tbl; -- => tbl ist jetzt leer!
Ergebnis: Die Archivierung schlägt jetzt nicht mehr fehl.