• webman 事务回滚失效问题记录


    webman 事务回滚失效问题记录

    简单介绍下webman

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

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

    是否推荐大家使用

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

    事务回滚失效问题排查

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

    1. return Db::transaction(function () use ($order, $explain) {
    2. $order->refund_status = OrderEnum::ORDER_REFUND_STATUS_ING;
    3. $order->save();
    4. $orderRefundModel = new OrderRefund();
    5. $orderRefundModel->refund_id = 'R' . date('YmdHis') . random_int(10000, 99999);
    6. $orderRefundModel->order_id = $order->order_id;
    7. $orderRefundModel->status = OrderEnum::ORDER_REFUND_APPLY_STATUS_ING;
    8. $orderRefundModel->refund_price = $order->paid_amount;
    9. $orderRefundModel->refund_num = 1;
    10. $orderRefundModel->refund_phone = $order->user_mobile;
    11. $orderRefundModel->refund_explain = $explain;
    12. $orderRefundModel->save();
    13. return $orderRefundModel->id;
    14. });

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

    问题的原因

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

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

    1. namespace app\model;
    2. use support\Model;
    3. class User extends Model
    4. {
    5. // 这里给模型指定了连接
    6. protected $connection = 'mysql';
    7. protected $table = 'users';
    8. protected $primaryKey = 'id';
    9. }

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

    1. Db::connection('mysql')->beginTransaction();
    2. try {
    3. // 业务处理
    4. $user = new User;
    5. $user->name = 'webman';
    6. $user->save();
    7. Db::connection('mysql')->commit();
    8. } catch (\Throwable $exception) {
    9. Db::connection('mysql')->rollBack();
    10. }

    排错之路

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

    打开查询日志配置

    1. [mysqld]
    2. general_log = ON
    3. # 这个日志文档路径必须要有权限写入,否则general_log打开失败
    4. general_log_file = /path/to/your/logfile.log
    5. SHOW 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 源码:

    1. /**
    2. * Get a database connection instance.
    3. *
    4. * @param string|null $name
    5. * @return \Illuminate\Database\Connection
    6. */
    7. public function connection($name = null)
    8. {
    9. [$database, $type] = $this->parseConnectionName($name);
    10. $name = $name ?: $database;
    11. // If we haven't created this connection, we'll create it based on the config
    12. // provided in the application. Once we've created the connections we will
    13. // set the "fetch mode" for PDO which determines the query return types.
    14. if (! isset($this->connections[$name])) {
    15. $this->connections[$name] = $this->configure(
    16. $this->makeConnection($database), $type
    17. );
    18. }
    19. return $this->connections[$name];
    20. }
    21. /**
    22. * Parse the connection into an array of the name and read / write type.
    23. *
    24. * @param string $name
    25. * @return array
    26. */
    27. protected function parseConnectionName($name)
    28. {
    29. $name = $name ?: $this->getDefaultConnection();
    30. return Str::endsWith($name, ['::read', '::write'])
    31. ? explode('::', $name, 2) : [$name, null];
    32. }
    33. /**
    34. * Make the database connection instance.
    35. *
    36. * @param string $name
    37. * @return \Illuminate\Database\Connection
    38. */
    39. protected function makeConnection($name)
    40. {
    41. $config = $this->configuration($name);
    42. // First we will check by the connection name to see if an extension has been
    43. // registered specifically for that connection. If it has we will call the
    44. // Closure and pass it the config allowing it to resolve the connection.
    45. if (isset($this->extensions[$name])) {
    46. return call_user_func($this->extensions[$name], $config, $name);
    47. }
    48. // Next we will check to see if an extension has been registered for a driver
    49. // and will call the Closure if so, which allows us to have a more generic
    50. // resolver for the drivers themselves which applies to all connections.
    51. if (isset($this->extensions[$driver = $config['driver']])) {
    52. return call_user_func($this->extensions[$driver], $config, $name);
    53. }
    54. return $this->factory->make($config, $name);
    55. }
  • 相关阅读:
    算法竞赛进阶指南0x35高斯消元与线性空间
    【C语言基础】Chap. 3. 操作符、关键字、#define和存储
    中宣部防沉迷系统PHP版本(管局防沉迷验证-PHP-全版本-接口测试样例)
    html中如何给input输入框这个一个默认值
    【笔记】samba shell 脚本 离线安装 - Ubuntu 20.04
    高速DSP系统设计参考指南(二)传输线(TL)效应
    Post-GWAS: LDSC 的 Partitioned Heritability 分析
    Codeforces Round #832 (Div. 2)「D 思维 + 异或前缀和 + 奇偶二分优化」
    Python 算法设计(2) - 大数运算 - 基于字符串的数字运算和进位
    zigbee笔记:七、zigbee系统电源管理与睡眠唤醒
  • 原文地址:https://blog.csdn.net/juan9872/article/details/138029234