utf8mb4使索引“突然变长”是因为其单字符最大占4字节,varchar(255)理论索引长度达1020字节,超出innodb默认767字节限制;laravel报sqlstate[22001]源于migrations表migration字段(varchar(255))在utf8mb4下建索引超限,推荐在appserviceprovider中调用schema::defaultstringlength(191)解决。

在Laravel项目中使用utf8mb4字符集时,MySQL索引长度超限会导致迁移失败、插入截断、查询变慢等连锁问题,尤其当字段定义为VARCHAR(255)且参与主键或唯一索引时,错误立刻暴露。
为什么utf8mb4会让索引“突然变长”
MySQL建索引时不看实际存了几个字,只按字段定义和字符集最大字节宽预估空间。utf8mb4每个字符最多占4字节,所以VARCHAR(255)理论索引长度是255 × 4 = 1020字节。
而InnoDB老版本默认索引上限是767字节——1020 > 767,直接报错“Specified key was too long”。哪怕你插的全是英文a-z,只要字段定义是utf8mb4,就按4字节算。
这和utf8(实为utf8mb3)完全不同:同样VARCHAR(255),utf8按3字节算,765字节刚好卡在767边界内,不会出错。
Laravel迁移报错SQLSTATE[22001]的根源
这个错误本质是migrations表的migration字段(默认string() → VARCHAR(255))在utf8mb4下建索引时超限。Laravel 5.4+默认启用utf8mb4,但没同步调整默认字符串长度。
方法一:全局降低默认字符串长度(推荐)
在app/Providers/AppServiceProvider.php的boot()方法中添加:
use Illuminate\Support\Facades\Schema;
Schema::defaultStringLength(191);
【191 × 4 = 764字节,严格小于767,确保所有string()字段可安全建索引】
方法二:手动重建migrations表(仅限开发环境)
DROP TABLE IF EXISTS migrations; → 再执行php artisan migrate
注意:不要用$text()替代$string(),因为migrations表结构不由你的迁移文件控制,改字段类型无效。
检查与验证是否生效
第一步:确认当前库字符集
SELECT DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'your_db_name';
第二步:查看migrations表真实结构
SHOW CREATE TABLE migrations;
第三步:重点核对migration字段是否变成VARCHAR(191),且ENGINE=InnoDB,CHARSET=utf8mb4。
如果仍显示VARCHAR(255),说明Schema::defaultStringLength(191)未生效——常见原因是boot()方法里漏了use语句,或该Provider未在config/app.php的providers数组中注册。
联合索引超限的隐蔽陷阱
单字段超限容易发现,但两个VARCHAR(100)字段合起来建联合索引,在utf8mb4下就是100×4 + 100×4 = 800字节,已超767限制。
这时MySQL不会报错,而是静默降级为前缀索引:只索引每个字段前若干字符。比如自动变成INDEX idx_name ON table(name(99), email(99))。
这种索引在WHERE name = ? AND email = ?时可能失效,导致全表扫描。必须用EXPLAIN验证执行计划,不能只看CREATE INDEX语句是否成功。
解决方式只有两个:要么缩减字段定义长度(如都设为VARCHAR(75)),要么开启innodb_large_prefix=ON并确保ROW_FORMAT=DYNAMIC(MySQL 5.7.7+默认已开,8.0无需配置)。











