当前位置:首页 » 《我的小黑屋》 » 正文

webman 事务回滚失效问题记录

0 人参与  2024年05月24日 19:45  分类 : 《我的小黑屋》  评论

点击全文阅读


webman 事务回滚失效问题记录

简单介绍下webman

webman是一款基于workerman开发的高性能HTTP服务框架。webman用于替代传统的php-fpm架构,提供超高性能可扩展的HTTP服务。你可以用webman开发网站,也可以开发HTTP接口或者微服务。

除此之外,webman还支持自定义进程,可以做workerman能做的任何事情,例如websocket服务、物联网、游戏、TCP服务、UDP服务、unix socket服务等等。

是否推荐大家使用

首先,小编不会推荐看到的朋友使用,不是因为它不好,而是因为有更多的选择.

事务回滚失效问题排查

一段很普通的代码逻辑:更新订单退款状态字段+新增退款申请记录.然后在一个事务里就行原子操作.

    return Db::transaction(function () use ($order, $explain) {            $order->refund_status = OrderEnum::ORDER_REFUND_STATUS_ING;            $order->save();            $orderRefundModel = new OrderRefund();            $orderRefundModel->refund_id = 'R' . date('YmdHis') . random_int(10000, 99999);            $orderRefundModel->order_id = $order->order_id;            $orderRefundModel->status = OrderEnum::ORDER_REFUND_APPLY_STATUS_ING;            $orderRefundModel->refund_price = $order->paid_amount;            $orderRefundModel->refund_num = 1;            $orderRefundModel->refund_phone = $order->user_mobile;            $orderRefundModel->refund_explain = $explain;            $orderRefundModel->save();            return $orderRefundModel->id;        });

这样的代码在我们的laravel项目上是一点问题没有,但是在webman框架使用却出现了事务回滚失败的问题.

问题的原因

其实这个问题还是因为小编没有认真阅读官方文档导致的,而上面的代码导致了一个什么错误让回滚失效的呢?

当事务中操作模型时,特别需要注意模型是否设置了连接。如果模型设置了连接开启事务的时候要指定连接,否则事务无效(think-orm类似)。例如

<?phpnamespace app\model;use support\Model;class User extends Model{    // 这里给模型指定了连接    protected $connection = 'mysql';    protected $table = 'users';    protected $primaryKey = 'id';}

当模型指定了连接时,开启事务、提交事务、回滚事务必须指定连接

Db::connection('mysql')->beginTransaction();try {    // 业务处理    $user = new User;    $user->name = 'webman';    $user->save();    Db::connection('mysql')->commit();} catch (\Throwable $exception) {    Db::connection('mysql')->rollBack();}

排错之路

通过代码是无法发现问题的,小编通过打开mysql 查询日志general_log来观察执行的sql

打开查询日志配置

[mysqld]general_log = ON# 这个日志文档路径必须要有权限写入,否则general_log打开失败general_log_file = /path/to/your/logfile.logSHOW VARIABLES LIKE 'general_log%';

将 general_log 设置为 ON表示启用查询日志,general_log_file 指定了日志文件的路径,重启 MySQL 服务器:在修改了配置文件后,需要重启 MySQL 服务器以使更改生效。查看日志文件:启用查询日志后,MySQL 会将所有执行过的 SQL 语句记录到指定的日志文件中。您可以查看该文件以获取所有 SQL 语句以及它们对应的连接信息。

查询日志

从上面的日志我们可以看到,事务和业务sql对应的线程ID是不一样的,那么这就是事务回滚两个业务sql不启用的原因了

为啥是不同的线程ID呢

线程ID不一样,肯定是两边分别创建了各自的连接,那我们就开始排查.通过查找了,我发现了webman admin 插件所有的model 都继承了Base类,而Base类指定了连接名,这下就破案了,也对应了官方的那句话:当模型指定了连接时,开启事务、提交事务、回滚事务必须指定连接

因此我们正确的代码:

Db::connection('plugin.admin.mysql')->transaction(...)

再看看连接的源码,默认的连接指向我们配置文件`database.php`的default属性值

DB manager 源码:

    /**     * Get a database connection instance.     *     * @param  string|null  $name     * @return \Illuminate\Database\Connection     */    public function connection($name = null)    {        [$database, $type] = $this->parseConnectionName($name);        $name = $name ?: $database;        // If we haven't created this connection, we'll create it based on the config        // provided in the application. Once we've created the connections we will        // set the "fetch mode" for PDO which determines the query return types.        if (! isset($this->connections[$name])) {            $this->connections[$name] = $this->configure(                $this->makeConnection($database), $type            );        }        return $this->connections[$name];    }    /**     * Parse the connection into an array of the name and read / write type.     *     * @param  string  $name     * @return array     */    protected function parseConnectionName($name)    {        $name = $name ?: $this->getDefaultConnection();        return Str::endsWith($name, ['::read', '::write'])                            ? explode('::', $name, 2) : [$name, null];    }    /**     * Make the database connection instance.     *     * @param  string  $name     * @return \Illuminate\Database\Connection     */    protected function makeConnection($name)    {        $config = $this->configuration($name);        // First we will check by the connection name to see if an extension has been        // registered specifically for that connection. If it has we will call the        // Closure and pass it the config allowing it to resolve the connection.        if (isset($this->extensions[$name])) {            return call_user_func($this->extensions[$name], $config, $name);        }        // Next we will check to see if an extension has been registered for a driver        // and will call the Closure if so, which allows us to have a more generic        // resolver for the drivers themselves which applies to all connections.        if (isset($this->extensions[$driver = $config['driver']])) {            return call_user_func($this->extensions[$driver], $config, $name);        }        return $this->factory->make($config, $name);    }

点击全文阅读


本文链接:http://zhangshiyu.com/post/113113.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1