MY.L2.COPY_ALTER_CHARSET — Character-set conversion that copies the table
- Category: safety
- Level: 2
- Confidence: deterministic
- Downtime class:
rewrite, derived from the online-DDL matrix - Stability: stable
- Suites: lint
- Applies to: MySQL 8.4
- Not transferable to MariaDB: this page describes MySQL 8.4 behavior. MariaDB answers the same driver and does not share these semantics, so SQLens refuses it outright rather than reasoning about it — see drivers/unsupported.
ALTER TABLE … CONVERT TO CHARACTER SET … re-encodes every value in every string column. InnoDB
therefore copies the whole table row by row under a shared lock: writes queue for the entire run,
and the run's length grows with the table.
It is the second great ALGORITHM=COPY trigger after a column type change, and the one a late move
to utf8mb4 walks straight into — on a latin1 table that has been accumulating rows for years,
which is precisely the table anyone runs this on.
The distinction that decides everything
Two statements look nearly identical and cost nothing alike. Measured against a real MySQL 8.4, by
running each under ALGORITHM=INSTANT and then ALGORITHM=INPLACE and recording which the server
accepts:
| Statement | MySQL 8.4 runs it | Existing rows |
|---|---|---|
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 | COPY | re-encoded |
ALTER TABLE t DEFAULT CHARACTER SET utf8mb4 | INPLACE | untouched |
ALTER TABLE t DEFAULT COLLATE … | INPLACE | untouched |
Setting the table's default character set is a statement about columns added in future, and
nothing else — verified directly: after DEFAULT CHARACTER SET utf8mb4, an existing latin1 column
is still latin1. SQLens does not report it. That silence is deliberate and is this rule's main
defense against crying wolf: flagging the cheap statement that looks like the expensive one is how a
linter teaches people to ignore it.
Flagged
DB::statement('ALTER TABLE orders CONVERT TO CHARACTER SET utf8mb4');
Preferred
// Convert one column at a time, so each deploy re-encodes less data and can be scheduled
// on its own — and let MySQL refuse rather than lock the table if it cannot stay online:
DB::statement('ALTER TABLE orders MODIFY note TEXT CHARACTER SET utf8mb4, LOCK=NONE');
Both halves are raw statements on purpose. Laravel's schema builder cannot express an existing-table
character-set change at all: $table->charset(…) inside a Schema::table block emits nothing
(measured, and pinned by a characterization test), so this migration only ever reaches your database
through DB::statement().
Where the boundary to the column rule runs
A column's character set can also change through MODIFY / CHANGE. That form belongs to
MY.L2.COPY_ALTER_TYPE, which owns column redefinitions whole — two
rules describing one statement is how a report starts contradicting itself. The column path is
covered, just not from here.
Sources
- InnoDB online DDL operations — MySQL 8.4
- Converting between character sets — MySQL 8.4
The fix material this rule carries
A finding from this rule carries machine-readable fix material, using this sequence:
charset_migration— Move the column to the new character set without a full-table conversion in place.
The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.