Věci, kterým se vyhnout, díl 1: INSERT IGNORE
Legacy code strikes back
Spoléhat se na nedokumentované funkce jazyka je poměrně riskantní. A když se to smíchá s INSERT IGNORE, jde to opravdu špatně.
Při jedné z aktualizací operačního systému na začátku roku 2016 byl Perl aktualizován z verze 5.14 na verzi 5.18. Bohužel se změnilo zpracování hashů, pořadí klíčů ve zcela specifickém scénáři už nebylo dodržováno a kód se rozbil. Máte pravdu, Perl pořadí nikdy nesliboval, ale náš kód spoléhal na některá specifika implementace.
Úryvek ze seznamu změn Perlu pro verzi 5.18:
Ve výchozím nastavení mohou nyní dvě různé hash proměnné se stejnými klíči a hodnotami poskytovat svůj obsah v jiném pořadí tam, kde byl dříve shodný.
Proč to byl problém? Kód připravoval seznam hashů pro vložení do databáze. Poté použil první položku k přípravě seznamu názvů sloupců, které budou předány následujícím dotazům INSERT. Každý odeslaný dotaz INSERT však používal jiné hashe pro přípravu seznamu hodnot k vložení. To vedlo k následujícímu:
INSERT IGNORE INTO foo (id, date) VALUES (1, ‚2006-10-11`), (‚2007-12-09‘, 2), …
Zásadně nebylo dodrženo pořadí názvů sloupců a příslušných hodnot. MySQL samozřejmě normálně hlásí chybu, když se někdo pokusí vložit datum do celočíselného sloupce. Ale vzpomínáte si na citaci z dokumentace MySQL, že? Tyto chyby se nevyvolávají, pokud se použije INSERT IGNORE a dojde k tiché konverzi dat:
Data konzistentně nekonzistentní
Skript po aktualizaci Perlu běžel v pořádku (no, alespoň jeho stderr byl tichý), ale po několika dnech jsme dostali hlášení o chybě, že něco není v pořádku s našimi interními nástroji, které používají tabulky poháněné Perlem. Soudě podle hlášení bylo něco dost podezřelé. A naše obavy potvrdil rychlý dotaz SELECT:
Tak co tu máme. Časové razítko a celočíselné hodnoty převedené na řetězec a vložené jako jméno uživatele, roky uložené v celočíselných sloupcích (je 2016 počet editací nebo rok poslední editace). Nekonzistence dat v nejlepší formě.
Nemusíte souhlasit, ale pro mě nejsou lepší žádná data než „data“ jako řádky výše.
Postižená tabulka váží přes 75 GiB a ukládá 460 mm řádků. Můžeme je přegenerovat, ale je to dlouhý proces. Skript běží ve chvíli, kdy čtete tento příběh. Tedy, běží už pět týdnů… To vše díky IGNORE v jediném dotazu.
Poučení
- INSERT IGNORE je tichá bestie, která jen čeká na vaši chybu, aby z dat udělala nesmyslný soubor hodnot.
- Nikdy neignorujte chyby. Dejte o nich hlasitě vědět. Selhávejte včas.
- Spoléhat se na nedokumentované vlastnosti jazyka (Perl a pořadí hashovacích klíčů je jen jedním z mnoha příkladů) je riskantní hra.
Byli jste varováni. Nyní zkontrolujte svůj kód nebo ngrep svůj provoz na dotazy INSERT IGNORE.