laravel项目中升级utf8到utf8mb4需分步操作:先备份,再收缩varchar字段至191字符以避免索引超限,重建索引,最后切换字符集;迁移文件需统一设string长度为191,并验证length与char_length是否相等以确认数据完整性。

在 Laravel 项目中将已有数据库表从 utf8 升级为 utf8mb4 字符集时,若直接执行 ALTER TABLE 修改字符集和排序规则,MySQL 可能因字段长度超限(如 VARCHAR(255) 在 utf8mb4 下索引超出 767 字节)而拒绝执行,或更危险的是——强制截断数据导致中文、Emoji 等四字节字符被静默丢弃,且无任何报错提示。
确认当前表结构与字符集现状
进入 MySQL 命令行,执行:
SELECT TABLE_NAME, ENGINE, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_database_name' AND TABLE_NAME IN ('users', 'posts');
对每个目标表,再查字段详情:
SHOW CREATE TABLE users;
重点观察:collation 是否为 utf8_general_ci 或 utf8_unicode_ci;varchar 字段是否普遍设为 255;是否有唯一索引或主键建在 varchar 字段上——这三者叠加正是数据截断高发区。
安全升级路径:分步收缩→重建索引→切换字符集
第一步:备份整库(不可跳过)
mysqldump -u root -p your_database_name > pre_utf8mb4_backup.sql
第二步:临时降低字符串字段长度,为 utf8mb4 索引腾出空间
ALTER TABLE users MODIFY email VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
第三步:逐个重建索引,确保新长度下索引可创建
DROP INDEX users_email_unique ON users;
CREATE UNIQUE INDEX users_email_unique ON users (email);
【关键】必须先 DROP 再 CREATE,不能用 ALTER TABLE ... ADD UNIQUE —— 否则 MySQL 仍按原长度校验,报错 1071 且不提示数据风险
第四步:批量处理其他表与字段
对 posts 表的 title 字段(假设原为 VARCHAR(255)):
ALTER TABLE posts MODIFY title VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DROP INDEX posts_title_index ON posts;
CREATE INDEX posts_title_index ON posts (title);
迁移文件内统一适配(防新增表再踩坑)
方法一:全局默认长度锁定
打开 app/Providers/AppServiceProvider.php,在 boot() 方法中添加:
Schema::defaultStringLength(191);
方法二:针对性重写迁移文件(适用于已存在迁移且需重跑)
找到 database/migrations/xxxx_create_users_table.php,将 $table->string('email') 改为 $table->string('email', 191);
方法三:对现有迁移追加兼容性修正
新建一个迁移文件:
php artisan make:migration fix_existing_tables_for_utf8mb4
在 up() 中写:
Schema::table('users', function (Blueprint $table) {
$table->string('email', 191)->change();
});
注意:使用 ->change() 需提前安装 doctrine/dbal 扩展,否则报错 “Doctrine DBAL not installed”。
验证升级结果与数据完整性
执行 SELECT LENGTH(email), CHAR_LENGTH(email), email FROM users WHERE email LIKE '%中%' OR email LIKE '%?%' LIMIT 5;
对比 LENGTH 和 CHAR_LENGTH:若两者相等,说明字符未被截断(utf8mb4 下中文/Emoji 占 4 字节,LENGTH 应为 CHAR_LENGTH × 4);若 LENGTH 小于预期,证明已有数据丢失,需从备份恢复。
最后运行 php artisan migrate:status,确认所有迁移状态为 “Ran”。











