Things To Avoid, Episode 1: INSERT IGNORE
Legacy code strikes back
Det är ganska riskabelt att förlita sig på odokumenterade språkfunktioner. Och när det blandas med INSERT IGNORE går det riktigt illa.
Under en av OS-uppgraderingarna i början av 2016 uppgraderades Perl från 5.14 till 5.18. Tyvärr ändrades hash-hanteringen, nyckelordningen i ett ganska specifikt scenario bibehölls inte längre och koden gick sönder. Du har rätt, Perl lovade aldrig ordningen, men vår kod förlitade sig på vissa specifika implementationer.
Ett utdrag ur Perls ändringslogg för v5.18:
Som standard kan två distinkta hash-variabler med identiska nycklar och värden nu tillhandahålla sitt innehåll i en annan ordning där det tidigare var identiskt.
Varför var det ett problem? Koden förberedde en lista med hashvariabler som skulle föras in i databasen. Sedan använde den det första objektet för att förbereda listan över kolumnnamn som ska skickas till de INSERT-förfrågningar som kommer att följa. Varje INSERT-fråga som skickades använde dock olika hash-koder för att förbereda listan över värden som skulle infogas. Detta ledde till följande:
INSERT IGNORE INTO foo (id, date) VALUES (1, ’2006-10-11`), (’2007-12-09’, 2), …
För det mesta hölls inte ordningen på kolumnnamn och respektive värden. Naturligtvis ger MySQL normalt upphov till ett fel när man försöker infoga ett datum i en heltalskolumn. Men du kommer väl ihåg citatet från MySQL-dokumentationen? De här felen tas inte upp när INSERT IGNORE används och tyst datakonvertering sker:
Data konsekvent inkonsekvent
Skriptet körde bra efter Perl-uppgraderingen (ja, åtminstone var dess stderr tyst), men efter några dagar fick vi en felrapport om att något är fel i våra interna verktyg som använder Perl-drivna tabeller. Att döma av rapporten var något ganska skumt. Och våra farhågor bekräftades av en snabb SELECT-förfrågan:
Så vad har vi här. Tidsstämpel och heltalsvärden som konverteras till sträng och läggs in som användarnamn, år som lagras i heltalskolumner (är 2016 edits count eller ett år för den senaste redigeringen). Datainkonsistens när den är som bäst.
Du behöver inte hålla med, men för mig är inga data bättre än ”data” som raderna ovan.
Den berörda tabellen väger över 75 GiB och lagrar 460 mm rader. Vi kan återskapa dem, men det är en lång process. Skriptet körs när du läser denna berättelse. Tja, det har redan körts i fem veckor… Allt tack vare IGNORE i en enda fråga.
Lärdomar
- INSERT IGNORE är ett tyst odjur som bara väntar på ditt misstag för att förvandla data till en meningslös uppsättning värden.
- Ignorera aldrig fel. Gör dem högljudda. Fail early.
- Att förlita sig på odokumenterade språkfunktioner (Perl och hash keys order är bara ett av många exempel) är ett riskabelt spel.
Du har blivit varnad. Kontrollera nu din kod eller ngrep din trafik för INSERT IGNORE-förfrågningar.