网站首页 > 知识剖析 正文
大家好,我是yangyang.最近有用到webman这个框架写业务,写代码的过程中,遇到了一个奇葩的问题:基于webman下使用laravel的orm组件事务回滚不生效
简单介绍下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类似)。例如
<?php
namespace 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.log
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 源码:
/**
* 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);
}
总结
- 仔细阅读官方文档!仔细阅读官方文档!仔细阅读官方文档!(重要的说三遍)
- 多线程(多进程)环境一定要关注是否是同一个连接(swoole、workerman等)
- 通过sql 查询日志、慢日志分析问题
猜你喜欢
- 2025-04-30 WordPress使用CDN后显示评论真实IP
- 2025-04-30 如何在WordPress文章分页中添加阅读全文按钮
- 2025-04-30 提权之disable_functions(三)(提权分为哪几个种类)
- 2025-04-30 ThinkPHP后台入口地址查找(thinkphp .htaccess)
- 2025-04-30 Thinkphp3.2.3及以下版本漏洞整理
- 2025-04-30 「某CMS漏洞」SQL注入漏洞分析(sql注入漏洞代码)
- 2025-04-30 php实现一个基本的 MVC 框架(php的mvc设计模式)
- 2025-04-30 PHP漏洞之跨网站请求伪造(php伪造来路跨域跳转)
- 2025-04-30 实现AI API Tokens计费产品方案和技术实现方案设计(PhalApi框架)
- 2025-04-30 习惯了各种框架的文件上传,php原生上传图片你还记得吗?
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)