Az elkerülendő dolgok, 1. rész: INSERT IGNORE
A hagyatéki kód visszavág
A dokumentálatlan nyelvi jellemzőkre támaszkodni meglehetősen kockázatos. És amikor ez INSERT IGNORE-val keveredik, a dolgok nagyon rosszra fordulnak.
A 2016 elején az egyik operációs rendszer frissítés során a Perl 5.14-ről 5.18-ra frissült. Sajnos a hash-ok kezelése megváltozott, a kulcsok sorrendje egy egészen speciális forgatókönyvben már nem volt karbantartva, és a kód elromlott. Igazad van, a Perl sosem ígérte meg a sorrendet, de a kódunk támaszkodott néhány végrehajtási sajátosságra.
Kivonat a v5.18-as Perl changelogból:
Előre két különböző hash-változó azonos kulcsokkal és értékekkel mostantól eltérő sorrendben adhatja meg a tartalmukat ott, ahol korábban azonos volt.
Mi volt a probléma? A kód az adatbázisba beszúrandó hash-ek listáját készítette elő. Ezután az első elemet a következő INSERT lekérdezéseknek átadandó oszlopnevek listájának előkészítésére használta. Azonban minden egyes elküldött INSERT lekérdezés különböző hash-okat használt a beszúrandó értékek listájának elkészítéséhez. Ez a következőkhöz vezetett:
INSERT IGNORE INTO foo (id, dátum) VALUES (1, ‘2006-10-11`), (‘2007-12-09’, 2), …
Az oszlopnevek és a hozzájuk tartozó értékek sorrendjét nem tartotta be. Természetesen a MySQL általában hibát jelez, ha egész szám oszlopba dátumot próbálunk beszúrni. De ugye emlékszel az idézetre a MySQL dokumentációjából? Ezek a hibák nem jelennek meg, ha az INSERT IGNORE-t használjuk, és csendes adatkonverzió történik:
Az adatok következetesen inkonzisztensek
A szkript a Perl frissítés után rendben futott (legalábbis az stderr-je csendes volt), de néhány nap múlva kaptunk egy hibajelentést, hogy valami baj van a Perl-alapú táblákat használó belső eszközeinkkel. A jelentésből ítélve valami eléggé gyanús volt. Aggodalmunkat pedig egy gyors SELECT lekérdezés igazolta:
Szóval mi van itt. Időbélyeg és egész szám értékek sztringgé konvertálva és felhasználói névként beillesztve, egész szám oszlopokban tárolt évek (a 2016-os szerkesztések száma vagy az utolsó szerkesztés éve). Adatkonzisztencia a javából.
Nem kell egyetértened, de számomra a fenti sorokhoz hasonló “adatoknál” nincs jobb adat.
Az érintett táblázat több mint 75 GiB súlyú és 460 mm sorokat tárol. Regenerálni tudjuk őket, de ez egy hosszú folyamat. A szkript fut, miközben ezt a történetet olvasod. Nos, már öt hete fut… Mindez az IGNORE-nak köszönhetően egyetlen lekérdezésben.
Tanulságok
- INSERT IGNORE egy csendes fenevad, amely csak a hibádra vár, hogy az adatokat értelmetlen értékhalmazzá változtassa.
- Soha ne hagyd figyelmen kívül a hibákat. Tegye őket hangossá. Fail early.
- A dokumentálatlan nyelvi tulajdonságokra támaszkodni (a Perl és a hash kulcsok sorrendje csak egy a számos példa közül) kockázatos játék.
Figyelmeztetlek. Most ellenőrizd a kódodat, vagy ngrepeld a forgalmadat INSERT IGNORE lekérdezésekre.