当前位置:   article > 正文

webman 事务回滚失效问题记录

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. <?php
  2. namespace app\model;
  3. use support\Model;
  4. class User extends Model
  5. {
  6. // 这里给模型指定了连接
  7. protected $connection = 'mysql';
  8. protected $table = 'users';
  9. protected $primaryKey = 'id';
  10. }

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

  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. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/467915
推荐阅读
相关标签
  

闽ICP备14008679号