You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

CI4 + PHP 8.1环境下mysqli_sql_exception触发时finally块未执行,但register_shutdown_function可正常执行的问题排查

CI4 + PHP 8.1环境下mysqli_sql_exception触发时finally块未执行,但register_shutdown_function可正常执行的问题排查

我来帮你拆解这个问题,结合CI4的内部机制和PHP的执行特性,核心原因和解决方案如下:

核心问题分析

  1. 错误的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()方法向上层传递异常。

  2. 生产模式下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()作为极端情况的兜底(比如致命错误、脚本被强制终止),确保锁文件不会残留。

验证步骤

  1. 先修正查询用法,运行代码看是否还会触发数据库异常;
  2. run()方法的catch块中添加日志输出,确认异常是否被上层捕获:
    catch (\Throwable $e2) {
        $result = $e2->getMessage();
        log_message('error', "[捕获到异常] " . $result); // 添加这行日志
        log_message('error', $result);
    }
    
    如果日志中没有出现[捕获到异常]的内容,说明异常被CI4全局错误处理拦截并终止了脚本,需要调整数据库异常配置或全局错误处理逻辑。

内容来源于stack exchange

火山引擎 最新活动