在使用 Laravel 数据库迁移的时候,可能会抛出一个异常:"SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes" (SQL: alter table `users` add unique `users_email_unique`(`email`))
。也就是在给 users
表的 email
字段添加唯一索引时,email
字段索引的长度超过了最大索引长度 767 字节。
原因
关于索引长度的限制,最主要的因素就是存储引擎和字符集。
字符集的影响在于,不同的字符集,单个字符包含的最大字节数有所不同。比如 utf8
字符集,一个字符最多包含 3 个字节。而 utf8mb4
一个字符最多包含 4 个字节。
对于 InnoDB 引擎来说:
1) Mysql 版本 <=5.7.6
- 如果是单字段索引,则字段长度不应超过 767 字节。
- 如果是联合索引,则每个字段长度都不应超过 767 字节,且所有字段长度合计不应超过 3072 字节。
这种情况下,常见字符集和引擎组合后的字符长度限制如下:
InnoDB | 最大字节数 | 单字段索引最大字符数 | 联合索引合计最大字符数 |
---|---|---|---|
lantin1 | 1 | 767 | 3072 |
gbk | 2 | 383 | 1536 |
utf8 | 3 | 255 | 1024 |
utf8mb4 | 4 | 191 | 768 |
2) Mysql 版本 >=5.7.7
在该版本情况下,由于 InnoDB 引擎的 innodb_large_prefix
等选项默认值改变,单字段索引长度限制增大。
- 如果是单字段索引,则字段长度不应超过 3072 字节。
- 如果是联合索引,则每个字段长度都不应超过 3072 字节,且所有字段长度合计不应超过 3072 字节。
这种情况下,常见字符集和引擎组合后的字符长度限制如下:
InnoDB | 最大字节数 | 单字段索引最大字符数 | 联合索引合计最大字符数 |
---|---|---|---|
lantin1 | 1 | 3072 | 3072 |
gbk | 2 | 1536 | 1536 |
utf8 | 3 | 1024 | 1024 |
utf8mb4 | 4 | 768 | 768 |
对于 MyISAN 引擎来说:
如果 Mysql 版本 <=5.5.4
则其默认存储引擎为 MyISAN。
- 如果是单字段索引,则字段长度不应超过 1000 字节。
- 如果是联合索引,则每个字段长度都不应超过 1000 字节,且所有字段长度合计不应超过 1000 字节。
这种情况下,常见字符集和引擎组合后的字符长度限制如下:
MyISAM | 最大字节数 | 单字段索引最大字符数 | 多字段索引合计最大字符数 |
---|---|---|---|
lantin1 | 1 | 1000 | 1000 |
gbk | 2 | 500 | 500 |
utf8 | 3 | 333 | 333 |
utf8mb4 | 4 | 250 | 250 |
测试语句样例
1 | CREATE TABLE `test` ( |
解决方法
修改存储引擎
修改字符集
修改索引字段的字符长度
以 Laravel 迁移为例,在 Mysql 版本 <=5.7.6
且字符集为 utf8mb4
的情况下,设置需要添加索引的字段长度为 191
:
1 | // database/migrations/2014_10_12_000000_create_users_table.php |
或者,直接设置 string
属性的字段的默认长度为 191
,只需要在 AppServiceProvider
类的 boot
方法中取消 Laravel 已经写好的注释即可:
1 | // app/Providers/AppServiceProvider.php |
- Mysql
<=5.7.6
的情况,修改 InnoDB 选项,将单字段索引长度限制由 767 字节(bytes)提高到 3072 字节。
先查看当前默认选项值:
1 | SHOW VARIABLES LIKE 'innodb_large_prefix'; |
确认选项值是否是:
1 | innodb_large_prefix=ON |
如果不是,进行相应的修改:
1 | SET GLOBAL innodb_large_prefix = 1; |
上面两项的值设置为 1
而不是 ON
,是因为在 Mysql5.6 之前,布尔值的变量还无法识别 ON
或者 OFF
,只能是数字 1
或者 0
。
并且,在创建新表时还要指定 ROW_FORMAT=DYNAMIC
或 ROW_FORMAT=COMPRESSED
如:
1 | CREATE TABLE `test` ( |
或修改当前表:
1 | ALTER TABLE [...], ROW_FORMAT=DYNAMIC; |
- 在创建索引时限制字段上的前缀索引长度:
1 | CREATE TABLE `test` ( |
参考文章
MySQL 经典案例分析:Specified key was too long
mysql change innodb_large_prefix
MySQL 5.5 innodb_large_prefix configuration setting not taking effect