线上API偶现There is already an active transaction异常

DreamWake

问题描述

线上API偶现There is already an active transaction异常,ORM框架使用的是think-orm,仔细检查过代码,开启事务后,有成对出现的commit和rollback,没有嵌套事务,请问如何排查和解决。

目前想进一步确认:
1.开启事务后和提交事务后,使用$pdo->inTransaction()检查是否在事务中,都返回true,这是否正常,这样检查可靠吗?
2.某进程的请求出现There is already an active transaction异常,是不是跟其他进程下的事务无关,排查问题时只关注这个进程的日志即可?

程序代码

精简后的method请求关键代码:

public function method() {
    Log::debug("请求处理开始",['node' => php_uname('n'),'pid'=>getmypid()]);
    $maxRetries = 3;
    for ($i=0;$i<$maxRetries;$i++){
        try {
            $this->method1();
            break;
        } catch (NeedRetryException $e) {
            Log::info('需要重试:'.$e->getMessage(),['node' => php_uname('n'),'pid'=>getmypid()]);
            usleep(($i+1) * 100 * 1000);// 延迟后再进行后续处理
        } catch (\Throwable $e) {
            Log::critical($e->getMessage(),['node' => php_uname('n'),'pid'=>getmypid()]);
            break;
        }
    }
    Log::debug("请求处理完成",['node' => php_uname('n'),'pid'=>getmypid()]);
}

private function method1() {
    Log::debug("下面开启事务",['node' => php_uname('n'),'pid'=>getmypid()]);
    Db::startTrans();
    $pdo = Db::connect()->getPdo();
    Log::info("startTrans", [
        'node' => php_uname('n'),
        'pid' => getmypid(),
        'pdo_id' => spl_object_id($pdo),
        'inTransaction' => $pdo->inTransaction()
    ]);
    try {
        ... 业务代码 ...

        Db::commit();
        Log::info("commit", [
            'node' => php_uname('n'),
            'pid' => getmypid(),
            'pdo_id' => spl_object_id($pdo),
            'inTransaction' => $pdo->inTransaction()
        ]);
    } catch (NeedRetryException $e) {
        Db::rollback();
        Log::info("rollback", [
            'node' => php_uname('n'),
            'pid' => getmypid(),
            'pdo_id' => spl_object_id($pdo),
            'inTransaction' => $pdo->inTransaction()
        ]);
        throw new NeedRetryException($e->getMessage());
    } catch (\Throwable $e) {
        Db::rollback();
        Log::info("rollback", [
            'node' => php_uname('n'),
            'pid' => getmypid(),
            'pdo_id' => spl_object_id($pdo),
            'inTransaction' => $pdo->inTransaction()
        ]);
        throw new Exception($e->getMessage());
    }

    return;
}

报错信息

关键日志如下:

[2025-12-18 10:38:46] default.DEBUG: 请求处理开始 {"node":"xxx","pid":11010} []
[2025-12-18 10:38:47] default.DEBUG: 下面开启事务 {"node":"xxx","pid":11010} []
[2025-12-18 10:38:47] default.CRITICAL: There is already an active transaction {"node":"xxx","pid":11010} []
[2025-12-18 10:38:47] default.DEBUG: 请求处理完成 {"node":"xxx","pid":11010} []
[2025-12-18 10:38:47] default.DEBUG: 请求处理开始 {"node":"xxx","pid":11010} []
[2025-12-18 10:38:48] default.INFO: 下面开启事务 {"node":"xxx","pid":11010} []
[2025-12-18 10:38:48] default.INFO: startTrans {"node":"xxx","pid":11010,"pdo_id":93,"inTransaction":true} []
[2025-12-18 10:38:48] default.INFO: commit {"node":"xxx","pid":11010,"pdo_id":93,"inTransaction":true} []
[2025-12-18 10:38:50] default.DEBUG: 请求处理完成 {"node":"xxx","pid":11010} []

截图报错信息里报错文件相关代码

操作系统及workerman/webman等框架组件具体版本

CentOS Linux release 8.1
php 8.2.16
workerman/webman-framework v1.5.16
workerman 4.1.15
webman/think-orm v1.1.1

425 2 0
2个回答

walkor 打赏

可能是有事务没提交,安装 webman/log 日志里会记录哪个请求没提交事务。

composer require webman/log

进程间是完全隔离的,进程间的事务不会有这种问题。

  • DreamWake 2026-01-04

    好的,谢谢!我试试。

  • DreamWake 2026-01-05

    安装 webman/log要求升级webman版本,我把webman版本从v1.5.16直接升级到v2.1.4后,发现定时任务的数据查询操作报错:

    [2026-01-04 18:06:30] default.ERROR: Call to a member function trigger() on null {"node":"xxx","pid":21636,"trace":"#0 D:\\prjPath\\vendor\\topthink\\think-orm\\src\\db\\BaseQuery.php(1479): think\\db\\PDOConnection->select(Object(think\\db\\Query))
    #1 D:\\prjPath\\vendor\\topthink\\think-orm\\src\\db\\Query.php(561): think\\db\\BaseQuery->select()
    #2 D:\\prjPath\\process\\sampleWorker.php(82): think\\db\\Query->chunk(50, Object(Closure))
    #3 D:\\prjPath\\process\\sampleWorker.php(46): process\\sampleWorker->sampleTask()
    #4 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Events\\Select.php(483): process\\sampleWorker->process\\{closure}()
    #5 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Events\\Select.php(340): Workerman\\Events\\Select->safeCall(Object(Closure), Array)
    #6 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Events\\Select.php(426): Workerman\\Events\\Select->tick()
    #7 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Worker.php(1620): Workerman\\Events\\Select->run()
    #8 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Worker.php(1536): Workerman\\Worker::forkWorkersForWindows()
    #9 D:\\prjPath\\vendor\\workerman\\workerman\\src\\Worker.php(603): Workerman\\Worker::forkWorkers()
    #10 D:\\prjPath\\runtime\\windows\\start_gravity-callback-handler.php(33): Workerman\\Worker::runAll()
    #11 {main}"} []

    请问是我升级方式不对吗,是否需要v1.5->v1.6->2.0->2.1逐版本升级?
    尝试了单独基于v2.1版本框架创建新项目,测试定时任务中使用数据库没发现问题。对比两个项目的基本框架部分,发现有部分差异,比如:创建的新项目process在app目录里面,从v1.5升级到v2.1的项目里没改变process的路径。

  • DreamWake 2026-01-05

    是按这个文档升级的,发现异常是在启动后,达到心跳检测间隔heartbeat_interval的时候抛出,但是我不清楚根本原因。

  • DreamWake 2026-01-05

    达到心跳检测间隔heartbeat_interval之前的查询正常的

  • walkor 2026-01-05
    composer require -W webman/think-orm:~2.1

    升级文档里的这个执行了没

  • walkor 2026-01-05

    执行完restart重启

  • DreamWake 2026-01-05

    升级执行过composer require -W webman/think-orm:~2.1,但是本地是Windows环境,项目没启动时进行的升级,没执行restart重启,是执行的php windows.php启动的项目。

  • walkor 2026-01-05

    看下 config下是不是有两个配置文件,think-orm.php和thinkorm.php ,保留一个,删除另外一个

  • DreamWake 2026-01-05

    只有一个thinkorm相关配置,对比过新建的项目,配置是think-orm.php,也尝试过只保留think-orm.php,配置都是生效的,能连上数据库数据查询正常。

  • DreamWake 2026-01-05

    目前发现本地Windows环境单独基于v2.1版本框架创建的新项目,测试定时任务中使用本地数据库没问题,但是使用云数据库会复现心跳检测时报错。

  • DreamWake 2026-01-05

    但是报错有点不同:

    PS D:\AnyProjects\webman-demo> php .\windows.php
    
    ---------------------------------------------- WORKERMAN -----------------------------------------------
    Workerman/5.1.7         PHP/8.2.0 (JIT off)           Windows NT/10.0
    ----------------------------------------------- WORKERS ------------------------------------------------
    worker                                          listen                              processes   status  
    monitor                                         none                                1           [ok]
    crontab                                         none                                1           [ok]
    webman                                          http://0.0.0.0:8788                 1           [ok]
    Error: Call to a member function execute() on bool in D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\db\PDOConnection.php:838
    Stack trace:
    #0 D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\db\PDOConnection.php(694): think\db\PDOConnection->getPDOStatement('select 1', Array, false)
    #1 D:\AnyProjects\webman-demo\vendor\webman\think-orm\src\DbManager.php(60): think\db\PDOConnection->query('select 1')
    #2 D:\AnyProjects\webman-demo\vendor\workerman\coroutine\src\Pool.php(328): Webman\ThinkOrm\DbManager->Webman\ThinkOrm\{closure}(Object(think\db\connector\Mysql))
    #3 D:\AnyProjects\webman-demo\vendor\workerman\coroutine\src\Pool.php(312): Workerman\Coroutine\Pool->trySendHeartbeat(Object(think\db\connector\Mysql))
    #4 D:\AnyProjects\webman-demo\vendor\workerman\coroutine\src\Pool.php(132): Workerman\Coroutine\Pool->checkConnections()
    #5 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(483): Workerman\Coroutine\Pool->Workerman\Coroutine\{closure}()#6 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(340): Workerman\Events\Select->safeCall(Object(Closure), Array)#7 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(426): Workerman\Events\Select->tick()
    #8 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(1620): Workerman\Events\Select->run()
    #9 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(1536): Workerman\Worker::forkWorkersForWindows()
    #10 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(603): Workerman\Worker::forkWorkers()
    #11 D:\AnyProjects\webman-demo\runtime\windows\start_crontab.php(33): Workerman\Worker::runAll()
    #12 {main}
    Error: Call to a member function parseKey() on null in D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\db\PDOConnection.php:1321
    Stack trace:
    #0 D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\db\concern\AggregateQuery.php(35): think\db\PDOConnection->aggregate(Object(think\db\Query), 'COUNT', '*', false)
    #1 D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\db\concern\AggregateQuery.php(51): think\db\BaseQuery->aggregate('COUNT', '*')    
    #2 [internal function]: think\db\BaseQuery->count()
    #3 D:\AnyProjects\webman-demo\vendor\topthink\think-orm\src\model\concern\DbConnect.php(196): call_user_func_array(Array, Array)
    #4 D:\AnyProjects\webman-demo\app\process\CrontabTask.php(22): think\Model::__callStatic('count', Array)
    #5 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(483): app\process\CrontabTask->app\process\{closure}()
    #6 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(340): Workerman\Events\Select->safeCall(Object(Closure), Array)#7 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Events\Select.php(426): Workerman\Events\Select->tick()
    #8 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(1620): Workerman\Events\Select->run()
    #9 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(1536): Workerman\Worker::forkWorkersForWindows()
    #10 D:\AnyProjects\webman-demo\vendor\workerman\workerman\src\Worker.php(603): Workerman\Worker::forkWorkers()
    #11 D:\AnyProjects\webman-demo\runtime\windows\start_crontab.php(33): Workerman\Worker::runAll()
    #12 {main}
    process D:\AnyProjects\webman-demo\runtime\windows\start_crontab.php terminated and try to restart
    crontab                                         none                                1           [ok]
  • walkor 2026-01-05

    新版本数据库命名空间有一点区别
    https://www.workerman.net/doc/webman/db/thinkorm.html

    use support\think\Db;
    use support\think\Model;
  • DreamWake 2026-01-05

    好的,谢谢!我改一下。

  • DreamWake 2026-01-05

    还是不行,我把新建的demo项目公开出来 https://gitee.com/DreamWakex/webman-demo

  • DreamWake 2026-01-05

    目前发现代码不动的情况下,配置使用本地MySQL数据库、阿里云RDB MySQL数据库正常,配置使用阿里云PolarDB-X分布式数据库时才有问题,所以可以说这个问题跟升级后的代码无关了。

  • DreamWake 2026-01-06

    分享一下进展。重写连接池等代码,加调试日志,配合wireshark抓包,最终发现连接PolarDB-X分布式数据库时,心跳检测报错的根本原因是框架执行SELECT 1时报错:#HY000[1aff133d31403000][11.246.52.178:3054][test_promotion]Prepare does not support sql: select 1,在重写的DbManager中心跳检测部分把检测的SQL改成select 1 as x解决。

  • walkor 2026-01-06

    更新 webman/think-orm 到2.1.9试下,做了兼容

  • DreamWake 2026-01-07

    好的

smile1

我遇到过 是不是用的云数据库
你是不是用的更新操作 save操作?

  • DreamWake 2026-01-05

    是连的云数据库,但不是在执行数据库操作的时候报错,是在心跳检测的时候报错。参考如下错误的think\db\PDOConnection->getPDOStatement('select 1', Array, false, false)

    Error: Call to a member function execute() on bool in D:\prjPath\vendor\topthink\think-orm\src\db\PDOConnection.php:813
    Stack trace:
    #0 D:\prjPath\vendor\topthink\think-orm\src\db\PDOConnection.php(749): think\db\PDOConnection->getPDOStatement('select 1', Array, false, false)
    #1 D:\prjPath\vendor\topthink\think-orm\src\db\PDOConnection.php(688): think\db\PDOConnection->pdoQuery(Object(think\db\Query), 'select 1', false)
    #2 D:\prjPath\vendor\webman\think-orm\src\DbManager.php(60): think\db\PDOConnection->query('select 1')
    #3 D:\prjPath\vendor\workerman\coroutine\src\Pool.php(328): Webman\ThinkOrm\DbManager->Webman\ThinkOrm\{closure}(Object(think\db\connector\Mysql))
    #4 D:\prjPath\vendor\workerman\coroutine\src\Pool.php(312): Workerman\Coroutine\Pool->trySendHeartbeat(Object(think\db\connector\Mysql))
    #5 D:\prjPath\vendor\workerman\coroutine\src\Pool.php(132): Workerman\Coroutine\Pool->checkConnections()
    #6 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(483): Workerman\Coroutine\Pool->Workerman\Coroutine\{closure}()
    #7 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(340): Workerman\Events\Select->safeCall(Object(Closure), Array)
    #8 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(426): Workerman\Events\Select->tick()
    #9 D:\prjPath\vendor\workerman\workerman\src\Worker.php(1620): Workerman\Events\Select->run()
    #10 D:\prjPath\vendor\workerman\workerman\src\Worker.php(1536): Workerman\Worker::forkWorkersForWindows()
    #11 D:\prjPath\vendor\workerman\workerman\src\Worker.php(603): Workerman\Worker::forkWorkers()
    #12 D:\prjPath\runtime\windows\start_gravity-callback-handler.php(33): Workerman\Worker::runAll()
    #13 {main}
    2026-01-05 15:37:20 sampleTask 开始
    Error: Call to a member function trigger() on null in D:\prjPath\vendor\topthink\think-orm\src\db\PDOConnection.php:967
    Stack trace:
    #2 D:\prjPath\process\sampleWorker.php(380): think\db\Query->chunk(50, Object(Closure))
    #3 D:\prjPath\process\sampleWorker.php(61): process\sampleWorker->sampleTask()
    #4 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(483): process\sampleWorker->process\{closure}()
    #5 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(340): Workerman\Events\Select->safeCall(Object(Closure), Array)
    #6 D:\prjPath\vendor\workerman\workerman\src\Events\Select.php(426): Workerman\Events\Select->tick()
    #7 D:\prjPath\vendor\workerman\workerman\src\Worker.php(1620): Workerman\Events\Select->run()
    #8 D:\prjPath\vendor\workerman\workerman\src\Worker.php(1536): Workerman\Worker::forkWorkersForWindows()
    #9 D:\prjPath\vendor\workerman\workerman\src\Worker.php(603): Workerman\Worker::forkWorkers()
    #10 D:\prjPath\runtime\windows\start_gravity-callback-handler.php(33): Workerman\Worker::runAll()
    #11 {main}
    process D:\prjPath\runtime\windows\start_gravity-callback-handler.php terminated and try to restart
    ^CTerminate batch job (Y/N)? y
  • smile1 2026-01-05

    There is already an active transaction 我说的是这个东西

  • DreamWake 2026-01-05

    哦哦,确实是有不少地方都是用的模型的save。也发现了save方法的源码实现会自动包裹事务,但是源码也有异常回滚,没看出save调用有什么影响。

  • smile1 2026-01-05

    你把save 改成 update 试试, 另外 检查是否有 报错后未rollback的代码

  • DreamWake 2026-01-05

    报错后未rollback检查过几遍,肉眼没发现问题,还是等升级新版框架后运行正常后查看 webman/log 日志。你之前遇到这个问题是通过save 改成 update解决的吗?改update还是模型的方式调用update方法吧,$model->save()改成$model->update(),而不是Db::where()->update()。

  • smile1 2026-01-05

    就模型调用 update 就行, save 方法是会比对修改的字段 原数据和修改数据是否一致,不一致才会更新,但是在云数据库的情况下,就会有问题,我们当时用的polardb, 读写分离是在云上控制的 会有这个问题

  • DreamWake 2026-01-05

    好的,感谢!我试试。

🔝