UTF-8 ukládá znaky jako vícebajtové (1-4 bajty), tedy znak může mít klidně 4 bajty. Bohužel, když při komunikaci s MySQL nastavíte kódování na UTF-8, ve skutečnosti se o plnohodnotné UTF-8 nejedná. Článek už mám nějakou dobu v šuplíku, dnešní hrabání se v kódu mě donutilo jej zveřejnit.
Podle dokumentace je utf-8 alias utf8mb3! To znamená, že interní interpretace UTF-8 má pouze tři bajty. Většinu času asi nenarazíte na problém, protože do utf8mb3 se vejde celá BMP ale narazíte, když uživatel zadá znaky, které v BMP nejsou jako například populární emoji. Při pokusu o uložení řetězce s takovým znakem, dostanete chybu podobnou této:
Warning | 1366 | Incorrect string value: ‚\xF0\x9D\x8C\x86‘ for column ‚description‘ at row 1
Nemožnost uložit emoji bohužel není jediný problém. Špatně nastavené kódování může útočníkovi zjednodušit práci při pokusu o napadení vaší aplikace – slidy z prezentace.
Nikdy při komunikaci s MySQL nebo pro vytváření tabulek/sloupců nepoužívejte utf-8! Vždy používejte utf8mb4! Utf8mb4 je v MySQL od verze 5.5.3, která vyšla na začátku roku 2010. Se starší verzí se snad už dneska nesetkáte (a rozhodně se ani setkat nechcete).
Převod všech tabulek a sloupců na utf8mb4 resp. utf8mb4_unicoce_ci lze udělat hromadně použitím údajů z tabulky information_schema.columns, kde je uložena informace o všech sloupcích. Po převedení je nejlepší nastavit správné kódování rovnou v konfiguraci MySQL, kde se stále používá jako výchozí utf8 :-(.
[client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-client-handshake = FALSE character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
kde character-set-client-handshake = false znamená, že má ignorovat požadované kódování vyžádané klientem. Asi bych raději nechal na true a po převedení všech tabulek/sloupců teprve nastavil na false (a znovu ověřil jestli se něco nerozbilo).
UPDATE 9.2.2019 Od verze MySQL 8 utf-8 znamená utf8mb4. Hurá!
Zdroje:
- https://www.eversql.com/mysql-utf8-vs-utf8mb4-whats-the-difference-between-utf8-and-utf8mb4/
- https://dev.mysql.com/doc/mysql-g11n-excerpt/8.0/en/charset-unicode-utf8mb3.html
- https://en.wikipedia.org/wiki/UTF-8
- https://en.wikipedia.org/wiki/Plane_(Unicode)
- https://speakerdeck.com/mathiasbynens/hacking-with-unicode?slide=98