在更新 Prod 环境的数据库时,运行了一个 alter table 语句,MySQL 报错:1071 - Specified key was too long; max key length is 767 bytes , 经过排查数据里的数据、字段和索引,并没有发现太明显的异常。
从错误信息来看,和索引是有关系的;但是本地数据库、test 环境数据库、uat 环境数据库都是正常的,只有 Prod 是有问题的;进过查询资料,定位到一个 MySQL 的配置参数可能和这个问题有关系
排错
在每个环境中查询 MySQL 参数:innodb_large_prefix
-- Test
show variables like 'innodb_large_prefix';
-- result: ON
-- UAT
show variables like 'innodb_large_prefix';
-- result: ON
-- Prod
show variables like 'innodb_large_prefix';
-- Result: OFF
innodb_large_prefix is deprecated; expect it to be removed in a future MySQL release. innodb_large_prefix was introduced in MySQL 5.5 to disable large index key prefixes for compatibility with earlier versions of InnoDB that do not support large index key prefixes.
The limits that apply to index key prefixes also apply to full-column index keys.
至于为什么最大限制是 767 Bytes,为什么和 innodb file format 与 row format 有关,有兴趣的可以再研究一下,和 Innodb 的存储结构、表空间、行记录格式等有关。
# 查看数据库的默认编码配置
mysql> show variables like 'char%';
+--------------------------+---------------------------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /Users/dfg/workspace/middleware/mysql/mysql-5.7.30-master/share/charsets/ |
+--------------------------+---------------------------------------------------------------------------+
8 rows in set (0.01 sec)
# 查看数据库 test 的编码
mysql> show create database test;
+----------+---------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+---------------------------------------------------------------------------------------------+
| test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */ |
+----------+---------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> CREATE TABLE `events_2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
`type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`key1` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
mysql> SET GLOBAL innodb_large_prefix = 0;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show variables like 'innodb_large_prefix';
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| innodb_large_prefix | OFF |
+---------------------+-------+
1 row in set (0.01 sec)
mysql> alter table events_2 add unique key uni_unique_id (uniqueId);
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
mysql> show create table events_3\G;
*************************** 1. row ***************************
Table: events_3
Create Table: CREATE TABLE `events_3` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`key1` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
mysql> alter table events_3 add unique key uni_unique_id (uniqueId);
Query OK, 0 rows affected (0.02 sec)
mysql> show index from events_3;
+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| events_3 | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | |
| events_3 | 0 | uni_unique_id | 1 | uniqueId | A | 0 | NULL | NULL | | BTREE | | |
+----------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
mysql> alter table events_3 character set utf8mb4 collate utf8mb4_unicode_ci;
Query OK, 0 rows affected (0.02 sec)
mysql> show create table events_3\G;
*************************** 1. row ***************************
Table: events_3
Create Table: CREATE TABLE `events_3` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`key1` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uni_unique_id` (`uniqueId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
mysql> show variables like 'innodb_file_format';
+--------------------+-----------+
| Variable_name | Value |
+--------------------+-----------+
| innodb_file_format | Barracuda |
+--------------------+-----------+
1 row in set (0.16 sec)
mysql> show variables like '%row_format%';
+---------------------------+---------+
| Variable_name | Value |
+---------------------------+---------+
| innodb_default_row_format | dynamic |
+---------------------------+---------+
mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.26 sec)
CREATE TABLE `events_1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
`type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`key1` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
CREATE TABLE `events_2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
`type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`key1` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
CREATE TABLE `events_3` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uniqueId` varchar(200) NOT NULL,
`type` varchar(32) NOT NULL,
`key1` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
alter table events_1 add unique key uni_unique_id (uniqueId);
show variables like 'innodb_large_prefix';
SET GLOBAL innodb_large_prefix = 0;
if innodb_large_prefix is enabled (the default), the index key prefix limit is 3072 bytes for InnoDB tables that use the or row format. If innodb_large_prefix is disabled, the index key prefix limit is 767 bytes for tables of any row format.
The index key prefix length limit is 767 bytes for InnoDB tables that use the or row format.
For example, you might hit this limit with a index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character.Attempting to use an index key prefix length that exceeds the limit returns an error.
To avoid such errors in replication configurations, avoid enabling innodb_large_prefix on the source if it cannot also be enabled on replicas.If you reduce the InnoDB to 8KB or 4KB by specifying the innodb_page_size option when creating the MySQL instance, the maximum length of the index key is lowered proportionally, based on the limit of 3072 bytes for a 16KB page size. That is, the maximum index key length is 1536 bytes when the page size is 8KB, and 768 bytes when the page size is 4KB.