MySQL "Incorrect string value": store emoji and everything else with utf8mb4
Fix MySQL error 1366 "Incorrect string value: \xF0\x9F...": utf8 only holds 3 bytes, so convert the table and connection to utf8mb4, and handle index length limits.
by OhSudo ·
ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'body' at row 1Someone typed an emoji, and the database refused it. The \xF0\x9F\x98\x80 is that character’s bytes, here 😀.
In MySQL, utf8 isn’t full UTF-8. It’s an alias for utf8mb3, which stores at most 3 bytes per character, and emoji and many other characters need 4. Real UTF-8 is called utf8mb4. It’s the default since MySQL 8.0, so older tables and upgraded databases are where you’ll find this error.
TL;DR
Back up first, then:
And connect with utf8mb4: charset=utf8mb4 in your application’s connection settings.
1. See what you have
The column’s own character set is what counts:
To list everything in the database that isn’t utf8mb4:
SELECT table_name, column_name, character_set_nameFROM information_schema.columnsWHERE table_schema = DATABASE() AND character_set_name IS NOT NULL AND character_set_name <> 'utf8mb4';And for the connection:
The bytes in the message hint at which case you have:
| Bytes begin with | Likely |
|---|---|
\xF0 |
A 4-byte character, usually an emoji, in a utf8mb3 column |
\xC3, \xE2 and similar |
Plain accented letters or € in a latin1 column, or a client sending another encoding |
2. Convert the table
This one converts the columns, along with their data:
To set the default for new tables too:
On MariaDB, or MySQL 5.7, use utf8mb4_unicode_ci, because the 0900 collations don’t exist there.
ALTER DATABASE doesn’t change the tables you already have, and neither does changing the server’s defaults. Each existing table has to be converted.
A table rewrite can take a long time and lock it. On a big table, test on a copy, take a backup first, and use a tool such as pt-online-schema-change or gh-ost.
3. Fix the connection
The connection has to use utf8mb4 too, or the data gets altered on the way:
| Client | Setting |
|---|---|
| Command line | mysql --default-character-set=utf8mb4 |
| PHP (PDO) | mysql:host=...;dbname=shop;charset=utf8mb4 |
| Python | charset='utf8mb4' |
Node.js (mysql2) |
charset: 'utf8mb4' |
| Java (JDBC) | characterEncoding=UTF-8 (Connector/J uses utf8mb4 for that) |
| Inside a session | SET NAMES utf8mb4; |
Importing a dump? Use mysql --default-character-set=utf8mb4 shop < dump.sql, with mysqldump --default-character-set=utf8mb4 on the way out.
And set defaults on the server, for everything new:
[mysqld]character-set-server = utf8mb4collation-server = utf8mb4_0900_ai_ci4. “Specified key was too long”
The conversion can fail with this on older setups:
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytesNow each character can take 4 bytes, so VARCHAR(255) means up to 1,020 bytes. MySQL 5.7 and 8.0 allow 3,072 bytes on tables with ROW_FORMAT=DYNAMIC, which is the default, so it’s mostly older tables that hit this:
Or index less of it:
A prefix index can’t enforce full uniqueness, though. For a UNIQUE column, the better fix is a shorter column. An email doesn’t need more than 254 characters, and VARCHAR(191) usually fits.
5. Query errors after converting
Two columns or a column and a connection using different collations give this:
ERROR 1267 (HY000): Illegal mix of collations (utf8mb3_general_ci,IMPLICIT) and (utf8mb4_0900_ai_ci,IMPLICIT) for operation '='So convert all related tables, particularly those you join on text columns. Mixed character sets on a join also stop it from using an index, so it gets slow without any error.
The new collation also changes comparisons. utf8mb4_0900_ai_ci ignores case and accents, so 'e' = 'é' is true. Your UNIQUE columns might now see two old values as equal, and the conversion stops with a duplicate key error. Find those first, or pick a different collation for that column.
6. Characters already show as garbage
Seeing é instead of é, or question marks? That data was already damaged. A common cause is a client sending UTF-8 over a latin1 connection. Converting the table won’t repair it, and can make it worse, so test on a copy first. Question marks are lost for good, as the original was replaced when it was stored.
Warning
Don’t “solve” this by turning off strict mode, as old answers suggest. Without STRICT_TRANS_TABLES, MySQL cuts the text off at the first bad character and only gives a warning, so you lose data with nobody noticing. Stripping emoji in your application only hides the problem, too.
Shell, we have a problem!
Similar, but different: ERROR 1406: Data too long for column is about length, not encoding, and VARCHAR(n) counts characters, not bytes. ERROR 1064 is a syntax error. On PostgreSQL, the equivalent is invalid byte sequence for encoding "UTF8", and its UTF8 is real UTF-8.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.