Skip to content
ohsudo, Home
Databases

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.

Beginner4 min readApplies to MySQL 5.7+ · MySQL 8.x · MariaDB 10.x

by OhSudo ·

ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'body' at row 1

Someone 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:

ALTER TABLE notes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

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:

SHOW FULL COLUMNS FROM notes;
SHOW CREATE TABLE notes\G

To list everything in the database that isn’t utf8mb4:

SELECT table_name, column_name, character_set_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND character_set_name IS NOT NULL
AND character_set_name <> 'utf8mb4';

And for the connection:

SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

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:

ALTER TABLE notes CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

To set the default for new tables too:

ALTER DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

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 = utf8mb4
collation-server = utf8mb4_0900_ai_ci

4. “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 bytes

Now 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:

ALTER TABLE users ROW_FORMAT=DYNAMIC;

Or index less of it:

CREATE INDEX idx_users_email ON users (email(191));

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.

Articles

Related articles