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.