根本原因是mysql连接层未设为utf8mb4,必须在config/database.php中mysql配置块显式设置'charset'=>'utf8mb4'、'collation'=>'utf8mb4_unicode_ci'及'options'=>[pdo::mysql_attr_init_command=>"set names utf8mb4 collate utf8mb4_unicode_ci"],并同步配置mysql服务端、已有表结构和迁移文件。

Laravel连接数据库时中文变问号、emoji存不进、报错SQLSTATE[HY000]: General error: 1366 Incorrect string value,根本原因不是数据库建表没设utf8mb4,而是MySQL连接层仍用着Laravel默认的utf8(即MySQL的伪UTF-8,最多3字节),它压根不认四字节的生僻字和emoji。
只改.env里的DB_CHARSET无效
别在.env里写DB_CHARSET=utf8mb4——Laravel官方配置文件根本没读这个变量,纯属误导。删掉这行,否则会给你一种“已经改了”的错觉。
真正起作用的是config/database.php中mysql连接块里的charset和collation字段。
打开config/database.php,找到'mysql' => [ ... ]这一段,在其中加入:【'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci'】。
这一步漏掉,后续所有操作都白搭。
必须显式执行SET NAMES命令
Laravel 8+在某些PDO版本下,即使你设了charset和collation,连接初始化时仍可能走默认utf8。必须补上PDO初始化指令。
在config/database.php的mysql配置块里,添加'options'数组:
'options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
]
注意:不要用DB::connection()->getPdo()->exec("SET NAMES...")这类运行时调用——它只影响当前连接,对php artisan migrate这种独立进程完全无效。
MySQL服务端未启用utf8mb4支持
哪怕Laravel连接层全设对了,如果MySQL本身不认utf8mb4,照样乱码。必须检查my.cnf(Linux)或my.ini(Windows)文件。
在[mysqld]段落下,确认存在这两行:
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
改完必须重启MySQL服务,否则配置不加载。
重启后,进MySQL命令行执行:
SHOW VARIABLES LIKE 'character_set%';
重点核对character_set_server和character_set_database两项,**必须都是utf8mb4**。只要有一个是utf8,就说明服务端没生效。
已有表和字段要手动升级
修改配置只影响新连接和新建表,旧表旧字段仍是utf8编码,数据一存就丢。
对每张已有表执行:
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
这条命令会同时转换表默认字符集、所有TEXT/VARCHAR字段、以及索引字段。只改表默认值(如ALTER TABLE users DEFAULT CHARACTER SET utf8mb4)是不够的,旧字段还是utf8。
如果表里有全文索引,需先DROP再重建,因为utf8mb4下全文索引长度限制不同。
迁移文件生成的表仍是utf8
Laravel 9.x之前,Schema::create()默认用utf8建表,哪怕你改了config/database.php也不生效——这是长期被忽略的隐性行为。
方法一:在迁移文件末尾追加强制语句
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
});
DB::statement("ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
方法二:全局设置(推荐)
在AppServiceProvider::boot()中加入:
Schema::defaultStringLength(191);
并确保所有新建迁移都显式声明字符集:
$table->string('title')->charset('utf8mb4');
这一步不做,新表建出来还是utf8,下次上线又踩坑。











