How to diagnose and fix character-set problems in MariaDB: the utf8 3-byte trap that loses emoji, the Illegal mix of collations error (1267) in JOINs, risky ALTER TABLE CONVERT TO CHARACTER SET operations, and connection-charset mismatches that produce mojibake.
Quick Reference
- In MariaDB,
utf8is an alias forutf8mb3(maximum 3 bytes per character).utf8mb3CANNOT store emoji or any code point above U+FFFF. ALWAYS useutf8mb4for any text that can hold user-generated content. Illegal mix of collations(error 1267) means two strings in a comparison or JOIN have different collations and neither can be coerced to the other. Fix with an explicitCOLLATEclause or by ALTERing the columns to the same collation.utf8mb4_uca1400_ai_ciis the modern default collation from MariaDB 11.5 onward. Before 11.5 the default wasutf8mb4_general_ci. Mixing these two in a JOIN triggers error 1267.ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4rewrites EVERY row and can grow indexed columns past the 767-byte index-prefix limit of theCOMPACTrow format. UseROW_FORMAT=DYNAMIC(limit 3072 bytes) before converting.- Set the connection charset with
SET NAMES utf8mb4. A connection charset that does not match the column charset produces mojibake (garbled text) even when the table is correct. Incorrect string value(error 1366) on INSERT means a 4-byte character was sent to autf8mb3(orlatin1) column. The fix is to convert the column toutf8mb4, not to strip the character.- MariaDB JSON is a
LONGTEXTalias, not native binary. UseCHECK (JSON_VALID(col))for structure; use functional indexes on virtual columns for index access. JSON columns inherit the table charset and should beutf8mb4. - NEVER use a
_bincollation for case-insensitive search:_bincompares raw bytes, so'A'never matches'a'.
Error Identification
| Symptom | Error number | Symbol | Meaning |
|---|---|---|---|
| Emoji or CJK extension character rejected on INSERT | 1366 | ER_TRUNCATED_WRONG_VALUE_FOR_FIELD |
A 4-byte character was sent to a utf8mb3 or latin1 column |
Illegal mix of collations (...) for operation on a JOIN, WHERE, or UNION |
1267 | ER_CANNOT_CONVERT_USING / cant-aggregate |
Two operands have incompatible collations with equal coercibility |
Specified key was too long; max key length is 767 bytes on CREATE INDEX or ALTER |
1071 | ER_TOO_LONG_KEY |
Indexed column prefix exceeds the row-format limit (767 on COMPACT) |
Stored text shows as ? characters |
(no error) | (silent) | Connection charset cannot represent the data; server replaced characters |
| Stored text shows as garbled multi-character sequences (mojibake) | (no error) | (silent) | Connection charset disagrees with the actual byte encoding |
Errors 1366 and 1071 happen at write/DDL time and are loud. Mojibake and ? substitution are SILENT data corruption: there is no error, only wrong bytes.
The utf8 vs utf8mb4 Trap
In MariaDB, the historic utf8 charset name is an alias for utf8mb3, which encodes a maximum of 3 bytes per character. The full UTF-8 standard needs up to 4 bytes; emoji, many CJK extension characters, and all supplementary-plane code points (above U+FFFF) require 4 bytes.
A column declared CHARACTER SET utf8 therefore CANNOT store an emoji. On insert MariaDB either truncates the string at the bad character or rejects the row with error 1366.
-- 10.6+ : WRONG, this column cannot hold emoji
CREATE TABLE comment_bad (body VARCHAR(500) CHARACTER SET utf8);
-- 10.6+ : CORRECT
CREATE TABLE comment_good (body VARCHAR(500) CHARACTER SET utf8mb4);
From MariaDB 10.6.1 the canonical name of the 3-byte set is utf8mb3; utf8 remains an alias. The old_mode system variable flag UTF8_IS_UTF8MB3 controls the alias target:
UTF8_IS_UTF8MB3set (orold_modeempty on current releases):utf8meansutf8mb3(3-byte).UTF8_IS_UTF8MB3cleared:utf8meansutf8mb4(4-byte).
NEVER rely on old_mode to make utf8 safe. The flag is environment-specific and old_mode options are deprecated by design. ALWAYS write utf8mb4 explicitly in DDL so the schema is unambiguous regardless of server configuration.
Decision Tree
Character-set problem
|
+-- Emoji / non-BMP character rejected (error 1366) or shows as '?'
| -> Column charset is utf8mb3 or latin1.
| Convert the column to utf8mb4 (see CONVERT TO section).
|
+-- "Illegal mix of collations" (error 1267) on JOIN / WHERE / UNION
| -> Two columns have different collations.
| Quick fix : add COLLATE to one operand in the query.
| Permanent fix : ALTER both columns to the same collation.
|
+-- "max key length is 767 bytes" (error 1071) on CREATE INDEX / ALTER
| -> Indexed column too wide for COMPACT row format under utf8mb4.
| Set ROW_FORMAT=DYNAMIC, or shorten the column / use an index prefix.
|
+-- Text is garbled (mojibake), no error
| -> Connection charset disagrees with the stored bytes.
| Run SET NAMES utf8mb4 on the connection; verify the
| application driver charset. Do NOT re-convert the column yet.
|
+-- New tables still default to latin1 / utf8mb3
-> Server default charset is old. Set character-set-server=utf8mb4
in my.cnf and declare DEFAULT CHARSET explicitly per table.
Collation Suffixes
A collation is a comparison-and-sort ruleset for a charset. The suffix encodes its behavior:
| Suffix | Meaning | Effect |
|---|---|---|
_ci |
Case-insensitive | 'A' = 'a' is TRUE |
_cs |
Case-sensitive | 'A' = 'a' is FALSE, accents distinguished |
_bin |
Binary | Raw byte comparison; 'A' = 'a' FALSE, no Unicode awareness |
_ai |
Accent-insensitive (10.10+) | 'e' = 'e-with-accent' is TRUE |
_as |
Accent-sensitive (10.10+) | accents distinguished |
_nopad |
No-pad (10.10+) | trailing spaces are significant characters |
utf8mb4_uca1400_ai_ci reads as: utf8mb4 charset, UCA 14.0.0 algorithm, accent-insensitive, case-insensitive. This is the MariaDB 11.5+ default.
NEVER use _bin for human-facing search: it misses every case and accent variant. Use _bin only for exact-match technical identifiers (tokens, hashes, base64).
Collation Family and Defaults
| MariaDB version | Default utf8mb4 collation |
|---|---|
| 10.6, 10.11, 11.0 - 11.4 | utf8mb4_general_ci |
| 11.5+ (incl. 11.6-LTS, 12.x) | utf8mb4_uca1400_ai_ci |
MariaDB 10.10 added 184 UCA-14.0.0 collations (utf8mb4_uca1400_*). They fix sort-order anomalies present in the legacy utf8mb4_general_ci and utf8mb4_unicode_ci.
The practical hazard: a table created on 10.11 has utf8mb4_general_ci columns; a table created on 11.6 has utf8mb4_uca1400_ai_ci columns. Joining a column from each triggers error 1267. See the Illegal Mix section.
See references/methods.md for the full charset/collation matrix per version.
Illegal Mix of Collations (Error 1267)
Error 1267 fires when a comparison, JOIN, UNION, or function call combines two string operands whose collations differ and neither has lower coercibility than the other. Coercibility ranks how "movable" a value's collation is (a literal is more coercible than a column; an explicit COLLATE is least coercible and wins).
-- Reproduce : two tables with different collations
CREATE TABLE a (name VARCHAR(50)) COLLATE utf8mb4_general_ci;
CREATE TABLE b (name VARCHAR(50)) COLLATE utf8mb4_uca1400_ai_ci;
-- 10.6+ : this JOIN fails with error 1267
SELECT * FROM a JOIN b ON a.name = b.name;
Two fixes:
-- Quick fix : force one side's collation in the query
SELECT * FROM a JOIN b
ON a.name = b.name COLLATE utf8mb4_uca1400_ai_ci;
-- Permanent fix : align the column collations
ALTER TABLE a MODIFY name VARCHAR(50)
CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci;
ALWAYS prefer the permanent fix. Per-query COLLATE clauses defeat indexes on the converted side and must be repeated in every query.
ALTER TABLE CONVERT TO CHARACTER SET
CONVERT TO CHARACTER SET rewrites every row and re-encodes every string column. Two hazards:
- It can change column data types. A
TEXTcolumn converts toMEDIUMTEXTunder utf8mb4 because each character now needs more bytes. - Indexed columns can exceed the row-format index-prefix limit.
| Row format | Max index-prefix per column (16k page) |
|---|---|
COMPACT, REDUNDANT |
767 bytes |
DYNAMIC, COMPRESSED |
3072 bytes |
An indexed VARCHAR(255) under utf8mb4 needs 255 x 4 = 1020 bytes. That exceeds 767 (COMPACT) and triggers error 1071, but fits within 3072 (DYNAMIC).
-- 10.6+ : safe full-table conversion
ALTER TABLE customers ROW_FORMAT=DYNAMIC;
ALTER TABLE customers CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_uca1400_ai_ci;
DYNAMIC is the default row format from MariaDB 10.2. On older 10.x the global innodb_large_prefix had to be ON to reach 3072 bytes; on 10.6+ the limit is governed solely by the row format, so innodb_large_prefix is no longer relevant.
NEVER run CONVERT TO on a large production table without measuring lock and disk impact first. It is a full table rebuild. See references/examples.md for an online-DDL approach.
Connection Charset
The stored bytes can be correct while the displayed text is garbled, because three connection variables decide how the client and server interpret strings:
| Variable | Role |
|---|---|
character_set_client |
charset of incoming query text and string literals |
character_set_connection |
charset queries are converted to for comparison |
character_set_results |
charset of result rows sent back to the client |
SET NAMES utf8mb4 sets all three to utf8mb4 in one statement. If the application sends utf8mb4 bytes but the connection is latin1, the server interprets each byte as a latin1 character and stores mojibake.
-- 10.6+ : run immediately after connecting
SET NAMES utf8mb4;
ALWAYS configure the charset in the driver connection options (for example charset=utf8mb4 in the DSN) rather than relying on a manual SET NAMES. The server default character-set-server does NOT change the charset of connections that negotiate their own.
See references/methods.md for per-driver connection-charset settings and references/anti-patterns.md for the mojibake anti-pattern.
Charset Hierarchy
Charset and collation cascade: column -> table -> database -> server. A column with no explicit charset inherits the table default; the table inherits the database; the database inherits character-set-server.
This cascade applies ONLY at object-creation time. Changing the server or database default does NOT retroactively convert existing tables. ALWAYS declare DEFAULT CHARSET=utf8mb4 per table and use CONVERT TO to fix existing tables.
# my.cnf : make new objects default to utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_uca1400_ai_ci
mysqldump Charset Safety
mariadb-dump (the renamed mysqldump, 10.5+) writes SET NAMES statements into the dump based on --default-character-set. A dump taken or restored with a mismatched charset corrupts every multi-byte string.
# 10.6+ : charset-safe dump and restore
mariadb-dump --default-character-set=utf8mb4 --single-transaction \
mydb > mydb.sql
mariadb --default-character-set=utf8mb4 mydb < mydb.sql
ALWAYS pass --default-character-set=utf8mb4 to BOTH the dump and the restore command. See references/examples.md for a charset-safe round-trip.
Detecting Non-utf8mb4 Columns
Audit a database for columns that still use a 3-byte or single-byte charset:
-- 10.6+ : list every column not on utf8mb4
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE CHARACTER_SET_NAME IS NOT NULL
AND CHARACTER_SET_NAME <> 'utf8mb4'
AND TABLE_SCHEMA NOT IN ('mysql','information_schema',
'performance_schema','sys');
See references/examples.md for a script that also reports collation mismatches across joined tables.
What This Skill Does NOT Cover
- General schema design and charset choice for new tables: see
mariadb-impl-schema-design. - MySQL-to-MariaDB migration end to end: see
mariadb-impl-migration-mysql-to-mariadb. - JSON syntax and functions: see
mariadb-syntax-json. - Backup and restore tooling beyond charset flags: see
mariadb-impl-backup-recovery.
Reference Links
references/methods.md: charset/collation matrix per version, collation-suffix semantics, error-code reference (1267, 1366, 1071), connection-charset variables, ALTER CONVERT TO byte-limit math.references/examples.md: 10+ working examples (reproduce utf8 emoji failure, fix with utf8mb4, Illegal-mix reproduction and COLLATE fix, full-table CONVERT TO with ROW_FORMAT, connection charset, charset-safe mysqldump round-trip, INFORMATION_SCHEMA audit).references/anti-patterns.md: 8+ real anti-patterns (utf8 for user text, mixed-collation JOIN, CONVERT TO on COMPACT VARCHAR(255), connection mismatch, _bin for case-insensitive search, mysqldump without charset flag, collation change ignoring indexes, server-default assumed retroactive).
Source Verification
All facts in this skill were verified via WebFetch against:
mariadb.com/docs/server/reference/data-types/string-data-types/character-sets/unicode:utf8is an alias forutf8mb3(max 3 bytes),utf8mb4stores supplementary characters in 4 bytes, alias controlled byold_mode.mariadb.com/docs/server/server-management/variables-and-modes/old_mode:UTF8_IS_UTF8MB3flag (10.6.1+), when setutf8aliasesutf8mb3, when clearedutf8mb4;old_modedeprecated by design.mariadb.com/kb/en/supported-character-sets-and-collations/:utf8mb4_general_cidefault through 11.4,utf8mb4_uca1400_ai_cidefault from 11.5; 184 UCA-14.0.0 collations added in 10.10;_ci/_cs/_bin/_ai/_as/_nopadsuffix semantics.mariadb.com/docs/server/reference/data-types/string-data-types/character-sets/setting-character-sets-and-collations:SET NAMES,character_set_client/character_set_connection/character_set_results, charset cascade column->table->database->server,CONVERT TO CHARACTER SETwidens data types.mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-row-formats: 767-byte index-prefix limit onCOMPACT, 3072-byte limit onDYNAMIC(16k page),innodb_large_prefixsuperseded by row format.mariadb.com/kb/en/coercibility/: coercibility 0 (explicit) to 6 (ignorable),COLLATEclause resolves collation conflicts.- Vooronderzoek Cluster-1 and Cluster-3 : utf8 3-byte trap, latin1 lock-in on stock 10.6/10.11, default charset migrated to utf8mb4 in 11.6,
utf8mb4+innodb_default_row_format=dynamicrequirement.