Laravel 8迁移时DB::unprepared方法在共享托管服务器运行失败
问题原因分析
- 线上MySQL账号缺少对应权限:共享托管服务器的MySQL账号默认不会开放创建存储过程/函数的权限,这是最常见的报错原因。本地环境一般使用root账号,权限全开所以不会触发报错。
- SQL分隔符冲突:创建函数的语句内部包含分号,Laravel执行
DB::unprepared时默认用分号作为语句结束符,会提前截断SQL导致语法错误。 - 函数名权限冲突:如果线上环境之前已经创建过同名函数,当前账号没有
ALTER ROUTINE权限的话,CREATE OR REPLACE语法也会执行失败。 - MySQL版本差异:如果本地MySQL版本高于线上版本,部分语法不兼容也会触发报错。
对应解决方案
方案1:开通数据库权限
联系共享托管服务商,给当前项目使用的数据库账号开通CREATE ROUTINE、ALTER ROUTINE、EXECUTE三个权限,这是创建自定义MySQL函数的基础前提。
如果服务商不支持开放上述权限,需要将自定义函数的逻辑替换为PHP层代码实现,放弃使用MySQL自定义函数的方案。
方案2:处理SQL分隔符冲突
在创建函数的SQL前后手动指定分隔符,修改迁移up方法代码如下:
public function up() { DB::unprepared(" DELIMITER // CREATE OR REPLACE FUNCTION `get_classes`(subjects VARCHAR(140)) RETURNS varchar(200) BEGIN DECLARE classes VARCHAR(48); SET classes='something here'; RETURN classes; END // DELIMITER ; "); }
方案3:增加异常捕获排查具体错误
可以在迁移代码中增加异常捕获逻辑,输出原生MySQL错误信息方便定位问题:
public function up() { try { DB::unprepared(" DELIMITER // CREATE OR REPLACE FUNCTION `get_classes`(subjects VARCHAR(140)) RETURNS varchar(200) BEGIN DECLARE classes VARCHAR(48); SET classes='something here'; RETURN classes; END // DELIMITER ; "); } catch (\Illuminate\Database\QueryException $e) { // 打印原生错误信息,排查完成后可以删除该行 dd($e->getMessage()); } }
也可以直接登录线上MySQL命令行执行创建函数的SQL语句,得到的错误提示会比Laravel封装后的异常信息更直观。
方案4:兼容低版本MySQL
如果线上MySQL版本低于8.0,不支持CREATE OR REPLACE FUNCTION语法,可以修改为先删除同名函数再创建:
public function up() { DB::unprepared('DROP FUNCTION IF EXISTS get_classes'); DB::unprepared(" DELIMITER // CREATE FUNCTION `get_classes`(subjects VARCHAR(140)) RETURNS varchar(200) BEGIN DECLARE classes VARCHAR(48); SET classes='something here'; RETURN classes; END // DELIMITER ; "); }
内容的提问来源于stack exchange,提问作者Hardik Sisodia
相关产品推荐
相关产品推荐

