Character Encoding in Databases: MySQL utf8 vs utf8mb4
Choosing the right character encoding for your database is one of those decisions that's easy to get wrong and painful to fix later. The most notorious example is MySQL's utf8 charset — which is not actually UTF-8.
MySQL's utf8 Trap
MySQL's utf8 character set only supports characters up to 3 bytes in UTF-8. This covers the Basic Multilingual Plane (U+0000–U+FFFF) but excludes supplementary characters — including all emoji, which are encoded as 4-byte UTF-8 sequences. Attempting to store an emoji in a utf8 column silently truncates the row at the emoji character.
utf8mb4: The Real UTF-8
MySQL 5.5.3 introduced utf8mb4, which supports the full UTF-8 encoding including 4-byte sequences. The mb4 suffix stands for "multi-byte 4". This is what you should use for any text column that might contain emoji, modern Chinese characters, or other supplementary Unicode characters. The default collation utf8mb4_unicode_ci performs case-insensitive comparison using Unicode rules.
Migration Considerations
Migrating from utf8 to utf8mb4 requires changing the column, table, and database charset declarations. Index sizes may also need adjustment: VARCHAR(255) columns in utf8mb4 require up to 1020 bytes of index space (255 × 4), which can exceed MySQL's default row format limits. Use the ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED table options to accommodate this.
PostgreSQL and SQLite
PostgreSQL uses the label UTF8 for real UTF-8 and has supported the full 4-byte range for many years. SQLite stores all text as UTF-8 or UTF-16 natively and handles the full code space. For new projects, PostgreSQL or SQLite avoid the MySQL utf8/utf8mb4 confusion entirely. Whatever database you use, always declare your encoding explicitly and test with emoji and CJK characters early in development.