Bug #121120 INPLACE utf8mb3-to-utf8mb4 conversion corrupts unindexed CHAR in REDUNDANT tables

Submitted: 19 Aug 4:50 Modified: 19 Aug 4:51
Reporter: George Ma (OCA) Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: DDL Severity:S3 (Non-critical)
Version:9.7.2 OS:Any
Assigned to: CPU Architecture:Any

[19 Aug 4:50] George Ma
Description:
MySQL Community Server 9.7.2 can leave an InnoDB table physically inconsistent when an unindexed `CHAR(N)` column in `ROW_FORMAT=REDUNDANT` is changed from `utf8mb3` to `utf8mb4` with the same character length and `ALGORITHM=INPLACE`.

The ALTER succeeds, but old clustered-index records are not rewritten. Their fixed field remains 3 * N bytes, while the new InnoDB metadata expects 4 * N bytes. A subsequent `CHECK TABLE` reports that the PRIMARY B-tree is corrupted.

This was reproduced twice in independent fresh schemas on the official Oracle MySQL Community Server 9.7.2 Linux generic minimal binary (`MySQL Community Server - GPL`), started with `--no-defaults --skip-networking --mysqlx=0`. The server was running on Linux 5.10.134-007.ali5000.al8.x86_64, x86_64. The downloaded archive matched Oracle's published MD5 (`fd5dacf5177894fbe527f0cce132147e`) and its detached signature verified against the MySQL Release Engineering 2025 signing key.

Expected result: a successful ALTER must leave `CHECK TABLE` reporting `status OK`. If this conversion cannot be performed without rewriting the records, `ALGORITHM=INPLACE` must be rejected before metadata is committed.

Actual result: the ALTER succeeds, and `CHECK TABLE` reports corruption. The error log contains `Field 3 len is 60, should be 80` for the affected record.

How to repeat:
Run the following on a fresh MySQL Community Server 9.7.2 instance:

```sql
DROP DATABASE IF EXISTS bug_repro;
CREATE DATABASE bug_repro;
USE bug_repro;

SET NAMES utf8mb4;

CREATE TABLE t1 (
  id INT NOT NULL,
  c CHAR(20) CHARACTER SET utf8mb3 NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB ROW_FORMAT=REDUNDANT;

INSERT INTO t1 VALUES (1, REPEAT('δΈ­', 20));

ALTER TABLE t1
  MODIFY c CHAR(20) CHARACTER SET utf8mb4 NOT NULL,
  ALGORITHM=INPLACE;

CHECK TABLE t1;
```

Actual output:

```
Table             Op    Msg_type  Msg_text
bug_repro.t1      check Warning   InnoDB: The B-tree of index PRIMARY is corrupted.
bug_repro.t1      check error     Corrupt
```
[19 Aug 4:51] George Ma
The InnoDB row-format documentation states that fixed-length character columns in `REDUNDANT` format use the maximum byte length of the character set. Therefore `CHAR(20)` requires 60 bytes under `utf8mb3` and 80 bytes under `utf8mb4`; changing the character set changes the physical fixed-field size even though the character count remains 20.

The MySQL 9.7.2 source tag `mysql-9.7.2` (commit `008e09c2834b98143a8c067d4d225c90953050cf`) appears to classify this change as metadata-only for the reproducer:

- `Field_str::is_equal()` in `sql/field.cc:6161-6179` returns `IS_EQUAL_PACK_LENGTH` when the character set differs after comparing the maximum display width in code points, without comparing the maximum byte width.
- `sql/sql_table.cc:13128-13148` maps that result to `ALTER_COLUMN_EQUAL_PACK_LENGTH` when the column is neither indexed nor used by a partitioning expression.
- `INNOBASE_ALTER_NOREBUILD` explicitly includes `ALTER_COLUMN_EQUAL_PACK_LENGTH` in `storage/innobase/handler/handler0alter.cc:170-180`; it is not part of `INNOBASE_ALTER_REBUILD` at `handler0alter.cc:127-142`.
- During the no-rebuild commit path, `innobase_rename_or_enlarge_columns_cache()` at `handler0alter.cc:6534-6586` updates `col->len` to `cf->max_display_width_in_bytes()`. The cache is committed through this path at `handler0alter.cc:7763-7768`.

Consequently, the dictionary is changed from 60 to 80 bytes without rebuilding the pre-existing REDUNDANT record. The `Field 3 len is 60, should be 80` diagnostic and the corrupted PRIMARY B-tree reported by `CHECK TABLE` match that sequence.

This is distinct from [Bug #121115](https://bugs.mysql.com/bug.php?id=121115), which requires an online rebuild from DYNAMIC to REDUNDANT plus a concurrent row-log insert and fails in row-log record conversion. The reproduction above starts and remains in REDUNDANT format, uses no concurrent DML, and reaches the no-rebuild metadata commit path instead.

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.051 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.