CI4 + PHP 8.1环境下mysqli_sql_exception触发时finally块未执行,但register_shutdown_function可正常执行的问题排查
我来帮你拆解这个问题,结合CI4的内部机制和PHP的执行特性,核心原因和解决方案如下:
核心问题分析
错误的Model查询用法导致未被预期的异常
你在notifyLongRunningTask()里调用$PushSentLog->select()时,传递的是WHERE条件、排序和限制片段——这完全不符合CI4 Model中select()方法的设计!这个方法的作用是指定要查询的字段(比如select('id, name')),你把条件塞进去会生成完全无效的SQL(比如SELECT AND user_created = ... FROM ...),数据库执行时必然抛出mysqli_sql_exception。更关键的是,你自定义的
select()方法里的错误处理逻辑(if (!$query))根本不会触发:因为当数据库开启异常模式时,$db->query()执行失败会直接抛出异常,而不是返回false,代码会直接跳出select()方法向上层传递异常。生产模式下CI4的隐性脚本终止
你的项目处于生产模式(DBDebug = false),但如果数据库配置中开启了throwExceptions(或者CI4在某些场景下强制抛出异常),这个未被预期的异常可能会被CI4的全局错误处理机制拦截,直接调用exit()终止脚本。而PHP的finally块只有在try/catch的正常执行流程结束后才会运行,脚本被强制终止时会直接跳过finally;但register_shutdown_function()是PHP级别的全局钩子,无论脚本如何终止(包括exit、致命错误)都会执行,所以它能正常清理锁文件。
解决方案
1. 修正Model查询的错误用法
用CI4 Model查询构建器的正确语法来拼接条件,完全避免SQL语法错误:
// 替换原来错误的select调用 $PushSentLog_res = $PushSentLog->where('user_created', $row->user_created) ->where('name', 'notifyLongRunningTask') ->orderBy('id', 'DESC') ->first(); // 对应LIMIT 1,返回单条记录
2. 确保异常被正确捕获
如果你的自定义select()方法需要处理数据库异常,要改成用try-catch捕获,而不是仅检查$query是否为false:
public function select($fields = '*') { // 假设原方法接收字段参数 $db = \Config\Database::connect(); $sql = "SELECT {$fields} FROM ..."; // 正确拼接字段 try { $query = $db->query($sql); } catch (\mysqli_sql_exception $e) { $error = $e->getMessage(); $baseClass = get_parent_class($this); $this->log_it($error, $baseClass . " " . __FUNCTION__ . ' DB Error'); return ['result' => (string) $error]; } // 后续处理逻辑... }
3. 检查CI4数据库异常配置
打开app/Config/Database.php,确认数据库连接组的throwExceptions设置:
public $default = [ // ...其他配置 'throwExceptions' => false, // 设置为false,数据库失败时返回false而非抛出异常 'DBDebug' => false, ];
如果不需要主动捕获数据库异常,这个设置能让你的原有错误处理逻辑(检查$query是否为false)生效。
4. 保留兜底的锁文件清理
即使修复了上述问题,也可以保留register_shutdown_function()作为极端情况的兜底(比如致命错误、脚本被强制终止),确保锁文件不会残留。
验证步骤
- 先修正查询用法,运行代码看是否还会触发数据库异常;
- 在
run()方法的catch块中添加日志输出,确认异常是否被上层捕获:
如果日志中没有出现catch (\Throwable $e2) { $result = $e2->getMessage(); log_message('error', "[捕获到异常] " . $result); // 添加这行日志 log_message('error', $result); }[捕获到异常]的内容,说明异常被CI4全局错误处理拦截并终止了脚本,需要调整数据库异常配置或全局错误处理逻辑。
内容来源于stack exchange




