Home Database MySQL Incorrect String Value — Switch to utf8mb4
Intermediate 5 min · September 23, 2026

MySQL Incorrect String Value — Switch to utf8mb4

Fix MySQL error 1366 by converting the column chain to utf8mb4 so 4-byte emoji fit.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 10 min
  • A MySQL 5.7+ database with a text column you can inspect
  • CLI access to run SHOW CREATE TABLE and information_schema queries
  • Know which DSN or connector your app uses to connect
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Error 1366 means you sent a character the column can't store — almost always a 4-byte emoji hitting a column defined as utf8, which in MySQL holds only 3 bytes per character
  • Check the whole chain, not just the column: database default, table default, column charset, and connection charset must all speak utf8mb4 or the emoji dies somewhere
  • Fix it with ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, converting the default and every text column in one pass
  • Set the connection too: SET NAMES utf8mb4 or charset=utf8mb4 in the DSN, or the driver mangles your emoji into question marks before MySQL ever sees them
✦ Definition~90s read
What is MySQL Incorrect String Value Fix?

Error 1366 fires when strict mode refuses to store bytes that are illegal in the target column's character set. The showcase trigger is a 4-byte UTF-8 sequence — bytes starting F0–F4, like F0 9F 98 80 for U+1F600 — offered to a column declared CHARACTER SET utf8.

Imagine your apartment building's mailboxes have narrow slots that fit letters but not parcels.

MySQL's utf8 is really utf8mb3: at most 3 bytes per character, covering only Unicode's Basic Multilingual Plane. Emoji live in the supplementary planes and need 4 bytes, so they're rejected at the door. Without strict mode you'd get silent truncation instead — arguably worse, since names save wrong with no error at all.

Whether a character fits depends on a chain of five settings. The column charset decides storage and wins over everything above it; the table default applies to new columns; the database default applies to new tables; the server default applies to new databases.

Then the connection charset (character_set_client/connection) decides how MySQL interprets the bytes your app sends — a latin1 connection garbles emoji before storage rules even run. Collation (the _ci suffix) only controls comparison and sorting, never which bytes fit, but you set both together in practice.

Diagnosis is therefore a chain audit: SHOW FULL COLUMNS for the column, SHOW CREATE TABLE for the table default, information_schema for the database, and SHOW VARIABLES LIKE 'character_set_%' for the connection. The fix that sticks converts all four storage levels to utf8mb4 and sets the connection to match — any level left behind becomes the next incident.

Plain-English First

Imagine your apartment building's mailboxes have narrow slots that fit letters but not parcels. An emoji is a parcel — it needs 4 bytes while old utf8 slots accept only 3. MySQL error 1366 is the mail carrier handing the parcel back: doesn't fit. Switching to utf8mb4 installs bigger slots everywhere: on each mailbox (the column), the wall of boxes (the table), the building directory (the database), and the delivery truck (the connection). Miss any one and parcels still bounce.

ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1. It always arrives the same way: a launch, a campaign, or a celebrity signup — and suddenly a slice of your writes start failing. The hex bytes look like garbage, the column looks like every other text column, and the app code didn't change. What changed is your users: someone typed an emoji, and your schema can't hold it.

The trap is MySQL's naming. utf8 sounds like full UTF-8, but it's a 3-byte subset that excludes emoji, many CJK extensions, and mathematical symbols. Real UTF-8 in MySQL is called utf8mb4. If your tables were created years ago — or by a framework default nobody questioned — every text column is quietly 3-byte.

This guide walks the full fix: reading the byte sequence in the error, auditing the charset chain from server to connection, converting with ALTER ... CONVERT TO, aligning the client, and locking in defaults so the next launch doesn't page you. Every query below is safe to run read-only first.

Anatomy of Error 1366

The error message is unusually generous: it prints the offending bytes, the column, and the row. '\xF0\x9F\x98\x80' is the UTF-8 encoding of U+1F600 (grinning face) — four bytes, each shown escaped. The leading byte F0 announces a 4-byte sequence (F0–F4 range), so you know before running anything that a supplementary-plane character met a 3-byte column. When the bytes instead show C3xx pairs, you're looking at ordinary accented Latin — a different problem, usually a latin1 column, not emoji.

Whether 1366 appears at all depends on sql_mode. With STRICT_TRANS_TABLES (the default since 5.7), illegal bytes are a hard error and the statement fails. Without strict mode, MySQL truncates to the nearest storable prefix and logs a warning — your insert 'succeeds' while the user's name saves mangled. Strict mode turning this into a loud error is a gift: it converts silent data corruption into a pageable, fixable rejection.

Your first three queries set the baseline: character_set_% variables for the connection side, collation_% for comparison defaults, and @@sql_mode to confirm strictness. Capture them in the ticket — charset incidents always involve comparing before/after across layers, and you'll want the starting snapshot.

baseline_1366.sqlSQL
1
2
3
4
5
6
7
8
9
-- Connection side: how does MySQL interpret incoming bytes?
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';

-- Strict mode on? (ON = loud 1366; OFF = silent truncation)
SELECT @@GLOBAL.sql_mode, @@SESSION.sql_mode;

-- Prove a 4-byte character: 4 bytes, 1 character
SELECT '😀', LENGTH('😀'), CHAR_LENGTH('😀');
📊 Production Insight
A team chased client encoding for 90 minutes because nobody read the F0 lead byte. One LENGTH vs CHAR_LENGTH query would have named a 4-byte character in ten seconds.
🎯 Key Takeaway
Read the hex: F0–F4 lead bytes mean 4-byte characters, and strict mode makes the rejection loud instead of silently mangling data.

utf8 Is 3 Bytes — Emoji Need utf8mb4

MySQL's utf8 is an alias for utf8mb3: up to 3 bytes per character, covering Unicode's Basic Multilingual Plane (code points U+0000–U+FFFF). That plane holds most of the world's scripts — but emoji (U+1F600 and friends), some CJK extensions, historic scripts, and math symbols live above it in the supplementary planes and require 4 bytes. utf8mb4 stores up to 4 bytes per character: genuine, complete UTF-8. The names differ by two characters; the coverage differs by a million code points.

This history explains every legacy schema. Before MySQL 5.5.3 there was no utf8mb4 at all, so every tutorial, framework default, and migration template from that era wrote CHARACTER SET utf8. Those columns still run in production today, invisible until the first emoji arrives. SHOW FULL COLUMNS exposes them: any Collation starting utf8_ (without mb4) on a text column is a 1366 waiting for a launch day.

The byte math also previews the index trap you'll meet in section four. A VARCHAR(255) unique key costs 765 bytes under utf8 but 1020 under utf8mb4 — over the 767-byte limit of old InnoDB row formats. Know this before converting and the 1071 error becomes a planned step instead of a nasty surprise at midnight.

find_3byte_columns.sqlSQL
1
2
3
4
5
6
7
8
9
-- Which text columns are still 3-byte? (no mb4 = can't hold emoji)
SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'shop'
  AND DATA_TYPE IN ('varchar','text','mediumtext','longtext','char')
  AND CHARACTER_SET_NAME = 'utf8';

-- Single-column check
SHOW FULL COLUMNS FROM users LIKE 'bio';
🔥utf8 Means utf8mb3 in MySQL
In MySQL, utf8 is an alias for 3-byte utf8mb3 — not real UTF-8. Only utf8mb4 stores all of Unicode. Any tutorial that says CHARACTER SET utf8 is either ancient or wrong for emoji.
📊 Production Insight
An information_schema scan found 214 three-byte text columns across the fleet — every one a launch-day 1366. The audit query now runs in CI against every migration.
🎯 Key Takeaway
Treat every non-mb4 text column as a latent 1366; audit with information_schema and plan conversions before launches.

The Charset Chain: Server, Database, Table, Column, Connection

Five layers decide what happens to your emoji, and each has a default that inherits downward. The server default (character_set_server) applies to new databases. The database default applies to new tables. The table default applies to new columns. The column charset — the only one that governs storage for existing data — wins over all of them. And the connection charset governs interpretation of bytes in flight. A chain is only as strong as its weakest setting: utf8mb4 everywhere except one latin1 connection still mangles data.

The classic partial fix is converting the table default only: ALTER TABLE users DEFAULT CHARACTER SET utf8mb4 changes future columns but leaves every existing column at 3 bytes — and future you debugs the identical 1366 next quarter. The full fix converts stored columns too, which is exactly what ... CONVERT TO does. Verify each layer after the change; SHOW CREATE TABLE should show utf8mb4 on the table and on every text column, and character_set_client/connection should read utf8mb4.

New objects deserve defaults that make the chain self-maintaining: CREATE DATABASE with CHARACTER SET utf8mb4, server-level character-set-server=utf8mb4 in my.cnf, and framework migrations that never name a charset at all (inheriting the right one). Chains you don't have to think about don't break at 9 AM on launch day.

audit_chain.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Database default
SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME
FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'shop';

-- Table defaults
SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'shop';

-- Column storage charsets (the layer that decides)
SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'shop'
  AND CHARACTER_SET_NAME IS NOT NULL;

-- Table + columns in one view
SHOW CREATE TABLE users;
📊 Production Insight
A 'fixed' table 1366'd again next quarter — someone had converted only the table default, leaving 30 columns at 3 bytes. The chain audit now gates every charset ticket.
🎯 Key Takeaway
Audit all five layers; the column charset governs storage, the connection governs transit, and defaults-only fixes always recur.

Fix It With ALTER ... CONVERT TO

ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci is the one-pass fix: it rewrites the table default and converts every character column, preserving data. For a single column, ALTER TABLE users MODIFY bio VARCHAR(280) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci does the same surgically. Both rewrite stored bytes correctly — 3-byte content stays identical, and the column gains room for 4-byte characters. Verify with SHOW CREATE TABLE and a live emoji insert before declaring victory.

Two costs need planning. First, CONVERT TO rebuilds the table: on a 50M-row table that means minutes of I/O and, on older MySQL, write locks — schedule off-peak or use an online schema-change tool (gh-ost, pt-online-schema-change) with ALGORITHM=INPLACE where supported. Second, indexes grow: any unique key over long varchars can breach the 767-byte limit and fail with ERROR 1071 mid-conversion. Pre-check indexed varchar widths, and fix by shortening the key prefix, switching to DYNAMIC row format, or hashing long values.

Convert replicas and backups in the same window. A replica with mismatched charsets replicates fine (bytes are bytes) but diverges the moment anyone compares dumps; and a mysqldump taken without --default-character-set=utf8mb4 can reintroduce Mojibake on restore. Charset is fleet state, not single-table state.

convert_utf8mb4.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- One-pass fix: table default + every character column
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Surgical fix for one column (keeps length + nullability explicit)
ALTER TABLE users MODIFY bio VARCHAR(280)
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Verify storage layer + live round-trip
SHOW CREATE TABLE users;
INSERT INTO users (bio) VALUES ('launch day 😀');
SELECT bio, HEX(bio) FROM users ORDER BY id DESC LIMIT 1;
⚠ CONVERT Rebuilds — Plan 1071 and Locks
CONVERT TO rewrites the table (locks on old MySQL, I/O on all of it) and can fail with ERROR 1071 when 4-byte indexes exceed 767 bytes. Pre-check indexed widths and use online schema-change tools on big tables.
📊 Production Insight
A midday CONVERT on 800k rows finished in seconds and recovered 18% of signups instantly — but the same statement on the 50M-row events table needed gh-ost overnight. Size the method to the table.
🎯 Key Takeaway
CONVERT TO fixes storage in one pass — verify with SHOW CREATE TABLE plus a live emoji round-trip, and plan for rebuild locks and 1071 on big tables.

Fix the Connection: SET NAMES and Client Charset

Storage fixed, transit next. character_set_client and character_set_connection tell MySQL how to interpret incoming bytes; when the driver negotiates latin1, your utf8mb4 bytes get reinterpreted on arrival — producing either 1366 or, worse, silently stored Mojibake. SET NAMES utf8mb4 aligns all three session variables at once and is the fastest way to prove the connection is the remaining culprit: if the insert works after SET NAMES and fails without it, the DSN is the bug.

Make it permanent per stack. Node's mysql2 takes charset: 'utf8mb4'; SQLAlchemy URLs take ?charset=utf8mb4; JDBC needs characterEncoding=UTF-8; the mysql CLI takes --default-character-set=utf8mb4; and my.cnf pins it for every local tool with default-character-set under [client] and [mysql]. Set it in the same deploy as the conversion — a converted table with a latin1 connection is a Mojibake factory.

Verify from the app's runtime, not your laptop. Exec into a container and insert an emoji through the app's own connection path, then read back the HEX. Laptop CLI tests with the right flags prove nothing about what the driver negotiates in production. If a proxy like ProxySQL sits in the path, pin the charset there too — proxies negotiate their own client connection and can silently downgrade an mb4 app to latin1.

check_conn_charset.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Prove the CLI path with explicit charset
mysql --default-character-set=utf8mb4 -u app -p -h db-primary -e \
  "SHOW VARIABLES LIKE 'character_set_connection';"

# What do local tools negotiate by default?
grep -A3 '^\[client\]' /etc/mysql/my.cnf 2>/dev/null || echo 'no [client] charset pinned'

# App-side DSN checklist (set one per stack):
# Node mysql2:      { charset: 'utf8mb4' }
# SQLAlchemy:       mysql+pymysql://u:p@host/db?charset=utf8mb4
# JDBC:             jdbc:mysql://host/db?characterEncoding=UTF-8
# Django:           OPTIONS: {'charset': 'utf8mb4'}
📊 Production Insight
A converted table kept producing Mojibake — the Java service still negotiated latin1. One JDBC parameter ended it. Storage and transit must ship together.
🎯 Key Takeaway
SET NAMES proves it, DSN charset fixes it permanently — and verify through the app's own connection path.

Prevention: Defaults, Strict Mode, and Emoji Tests

End the class of incident, not the instance. Set fleet-wide defaults so new objects are born correct: character-set-server=utf8mb4 and collation-server=utf8mb4_unicode_ci in my.cnf, CREATE DATABASE ... CHARACTER SET utf8mb4 for every new schema, and migration templates that never specify 3-byte charsets. When the default is right, developers get utf8mb4 by doing nothing — which is the only default humans reliably follow.

Keep strict mode on everywhere, including local dev. STRICT_TRANS_TABLES converts would-be corruption into loud 1366s at the cheapest possible moment: on the developer's laptop, not at launch. Any environment running without it is manufacturing silent truncation debt that surfaces as mysteriously mangled names months later.

Add the emoji probe to CI and synthetics. A migration check that fails on any non-mb4 text column, plus a signup test that registers 😀🎉 and reads back the exact bytes, costs minutes to write and catches every regression — framework upgrades love silently reverting charset defaults. The launch that pages nobody is the one whose schema was already ready. Document the fleet charset standard next to your backup runbook so every new service inherits it.

defaults_emoji_test.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- New schemas born correct
CREATE DATABASE shop_new
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CI gate: zero 3-byte text columns allowed
SELECT TABLE_NAME, COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'shop'
  AND CHARACTER_SET_NAME = 'utf8';
-- (must return 0 rows or the pipeline fails)

-- Synthetic probe: emoji round-trip must be exact
INSERT INTO users (bio) VALUES ('probe 😀🎉');
SELECT bio FROM users WHERE bio = 'probe 😀🎉';
📊 Production Insight
An emoji signup probe in synthetics now catches charset regressions within minutes — including one framework upgrade that silently reverted the default.
🎯 Key Takeaway
Default everything to utf8mb4, keep strict mode loud, and gate CI plus synthetics on an exact emoji round-trip.
● Production incidentPOST-MORTEMseverity: high

Launch-Day Emoji Rejected 4,200 Signups in 3 Hours

Symptom
At 9:00 AM the profile launch went live with 'express yourself — emoji welcome!' copy. By 9:20 AM support saw signup failures; by noon, 4,200 registrations had died with ERROR 1366 on the bio column — 18% of attempts, all containing emoji. The signup API returned a generic 500, so mobile clients showed 'try again later' and users retried repeatedly, tripling the error volume. No alert fired for 40 minutes because the endpoint's baseline error budget covered the early trickle.
Assumption
The team blamed the mobile release: the emoji keyboard had shipped in the same build, so they suspected bad client encoding and rolled the app back at 10:30 AM. Failures continued on the old build — because users paste emoji from anywhere, not just the new keyboard. Another 90 minutes went to auditing API validation regexes that were never in the path of a database charset rejection.
Root cause
The users.bio column was VARCHAR(280) CHARACTER SET utf8 (3-byte), inherited from a 2019 migration, while the table and database defaults were also utf8. Any 4-byte character — emoji, some rare CJK, math symbols — was illegal in that column under STRICT_TRANS_TABLES, hence a hard 1366 instead of silent truncation. The staging database had been converted to utf8mb4 months earlier during an unrelated cleanup, so staging tests with emoji passed and masked the prod gap.
Fix
At 12:40 PM they ran ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci on the 800k-row table (fast, off-peak enough), plus SET NAMES utf8mb4 verification on the app DSNs — which already sent utf8mb4, so no client change was needed. Signups recovered instantly. A backfill wasn't required since failed rows never stored. Follow-ups: converted all remaining utf8 tables, pinned utf8mb4 in schema defaults, and added an emoji-bearing signup to the synthetic monitor.
Key lesson
  • Audit the whole charset chain in production, not staging: column, table, database, and connection must all agree, and a converted staging database will happily hide a 3-byte prod column.
  • Read the hex in the error: bytes starting F0–F4 name a 4-byte character instantly, ruling out app encoding bugs without touching client code.
  • Monitor business-shaped failures: a generic-500 signup probe with an emoji payload would have paged in minutes instead of letting 4,200 users bounce.
Production debug guideFive checks that walk the charset chain from the failing bytes to the layer that rejects them.5 entries
Symptom · 01
Error 1366 shows hex like '\xF0\x9F\x98\x80' for a column
Fix
Confirm it's a 4-byte character: SELECT LENGTH('😀'), CHAR_LENGTH('😀'); — LENGTH 4 with CHAR_LENGTH 1 proves a supplementary-plane character. Bytes starting F0–F4 always mean 4-byte UTF-8, so skip client debugging and go straight to the column charset: SHOW FULL COLUMNS FROM users LIKE 'bio'; If Collation shows utf8_ (not utf8mb4_), the column physically cannot store it.
Symptom · 02
You need the full chain: server, database, table, column, connection
Fix
Run one audit pass: SHOW CREATE TABLE users; for the table default and column clauses, then SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='shop'; then SHOW VARIABLES LIKE 'character_set_%'; Every storage level should read utf8mb4 and character_set_client/connection should too — the first level that says utf8 or latin1 is your culprit.
Symptom · 03
Column is utf8mb4 but inserts still fail with 1366
Fix
Suspect the connection: SHOW VARIABLES LIKE 'character_set_client'; SHOW VARIABLES LIKE 'character_set_connection'; If either is latin1 or utf8, the driver is transliterating before MySQL sees the bytes — fix with SET NAMES utf8mb4; for the session test, then permanently via charset=utf8mb4 in the DSN (Node mysql2, SQLAlchemy ?charset=utf8mb4, JDBC characterEncoding=UTF-8).
Symptom · 04
ALTER CONVERT fails with ERROR 1071: specified key was too long
Fix
A 4-byte charset quadruples index byte size: VARCHAR(255) unique becomes 1020 bytes, over the 767-byte limit on old row formats. Check with SHOW TABLE STATUS and SHOW CREATE TABLE; fix by upgrading to DYNAMIC row format (innodb_large_prefix era is default in 5.7.7+), shortening the indexed prefix with INDEX bio_prefix (bio(191)), or hashing long values into a fixed column.
Symptom · 05
Old rows show Mojibake like '😀' instead of emoji
Fix
That's double encoding: UTF-8 bytes stored through a latin1 connection, then read as UTF-8. Confirm with SELECT bio, HEX(bio) FROM users WHERE id=...; — C3B0-style doubled sequences prove it. Fix the connection first, then repair with CONVERT(CAST(CONVERT(bio USING latin1) AS BINARY) USING utf8mb4) per row, and verify a round-trip insert of 😀 before closing.
MySQL 1366 Causes at a Glance
Root CauseHow to ConfirmFixPrevention
Column still 3-byte utf8SHOW FULL COLUMNS shows utf8_* collationALTER ... CONVERT TO utf8mb4CI gate: zero utf8 columns
Only table default convertedSHOW CREATE TABLE: default mb4, columns utf8CONVERT TO (rewrites columns, not just default)Always verify per-column, not just default
Connection charset latin1/utf8character_set_client/connection not mb4SET NAMES utf8mb4; charset in DSNPin charset in DSN + my.cnf [client]
Index too long after convert (1071)CONVERT fails: specified key was too longShorten prefix / DYNAMIC rows / hash columnPre-check indexed varchar widths
Old Mojibake from prior mismatchHEX shows doubled C3-style sequencesFix connection, then latin1→binary→mb4 repairNever mix converted tables with latin1 clients
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
baseline_1366.sqlSHOW VARIABLES LIKE 'character_set_%';Anatomy of Error 1366
find_3byte_columns.sqlSELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAMEutf8 Is 3 Bytes
audit_chain.sqlSELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAMEThe Charset Chain
convert_utf8mb4.sqlALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Fix It With ALTER ... CONVERT TO
check_conn_charset.shmysql --default-character-set=utf8mb4 -u app -p -h db-primary -e \Fix the Connection
defaults_emoji_test.sqlCREATE DATABASE shop_newPrevention

Key takeaways

1
Error 1366 means bytes illegal in the column charset
F0–F4 lead bytes name 4-byte emoji instantly.
2
MySQL utf8 is 3-byte utf8mb3; only utf8mb4 stores full Unicode.
3
Audit all five chain layers
column storage plus connection transit plus the defaults above.
4
CONVERT TO fixes storage in one pass; SET NAMES plus DSN charset fixes transit.
5
Plan conversions for rebuild locks and ERROR 1071 on long indexed varchars.
6
Default everything to utf8mb4, keep strict mode loud, and probe emoji in CI.

Common mistakes to avoid

5 patterns
×

Converting only the table default and declaring victory

Symptom
SHOW CREATE TABLE looks mb4 but inserts still 1366 — existing columns never changed.
Fix
Use CONVERT TO (rewrites columns) and verify every text column shows utf8mb4 in information_schema.
×

Forgetting the connection charset

Symptom
Storage is mb4 yet emoji save as ? or Mojibake — the driver transliterated in transit.
Fix
SET NAMES utf8mb4 to prove it, then pin charset=utf8mb4 in the DSN permanently.
×

Assuming utf8 means real UTF-8

Symptom
Schema reviews pass, then launch-day emoji 1366s — tutorials and old defaults lied.
Fix
Treat utf8 as utf8mb3 everywhere; grep migrations for CHARACTER SET utf8 without mb4.
×

Running CONVERT on a huge table at peak

Symptom
Table rebuild locks writes for minutes; a charset fix becomes an availability incident.
Fix
Use ALGORITHM=INPLACE or gh-ost/pt-online-schema-change off-peak for multi-million-row tables.
×

Disabling strict mode to make the error go away

Symptom
1366s stop but names save truncated — silent corruption instead of loud rejection.
Fix
Keep STRICT_TRANS_TABLES on; fix the charset so the loud error has nothing to report.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
A user can't save 😀 in a bio column. What's your first diagnosis?
Q02SENIOR
Describe the full charset chain you'd audit for error 1366.
Q03SENIOR
When do you use CONVERT TO versus MODIFY for a charset fix?
Q04SENIOR
Your CONVERT fails with ERROR 1071 (key too long). What happened and how...
Q05SENIOR
Rows show '😀' instead of emoji. Diagnose and repair.
Q01 of 05JUNIOR

A user can't save 😀 in a bio column. What's your first diagnosis?

ANSWER
It's a 4-byte character hitting 3-byte storage: check SHOW FULL COLUMNS for a utf8 (non-mb4) collation. Fix with CONVERT TO utf8mb4 plus connection charset alignment.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What's the difference between utf8 and utf8mb4?
02
Will converting to utf8mb4 damage existing data?
03
I converted but inserts still fail. Why?
04
What is ERROR 1071 during conversion?
05
Which collation should I pick with utf8mb4?
06
Does utf8mb4 hurt performance or storage?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Drawn from code that ran under real load.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's MySQL. Mark it forged?

5 min read · try the examples if you haven't

Previous
MySQL Lock Wait Timeout Fix
3 / 5 · MySQL
Next
MySQL Too Many Connections Fix