mysql连接池的理解及使用代码优化thinkphp6+swoole

        mysql连接池就是把创建的mysql的连接,通过swoole保存下来,下个线程直接用而不用再次连接了。

        事情是这么个事情,但是这个事情下其实有个潜在的逻辑,那就是多个php线程的mysql线程是一个,那也就是说,这多个php线程在mysql端的上下文是一个,也就是说,PHP-A线程开启了事务,如果没有提交,PHP-B线程执行了回滚,PHP-A的sql就被回滚了。

        PHP-A没有提交的原因大概有2个,1个是忘了,这个可以用tp的Db::transaction(function(){});去写,就没有忘的问题了。第二个原因是各种意外错误,比如访问一个远程接口超时了,整个访问都断了,后面的代码自然都没运行。

        这个问题本来也无所谓,因为mysql的不允许事务嵌套的,在pdo下,连续开启2次事务,第二次会报错“There is already an active transaction”,发现问题自然就去解决了,问题出在用了框架。

        关于事务的控制,thinkphp都在文件vendor\topthink\think-orm\src\db\PDOConnection.php。(我是在关闭事务嵌套的前提先去说这个事儿,否则太复杂了不方便说明。关闭事务嵌套是改这个方法“supportSavepoint”)

        这个文件用$this->transTimes来记录是否开启过事务,如果这个变量大于1,那就不执行开始事务的代码,只累加,commit和rollback也是同理,如果发现$this->transTimes不是1,那么就只累减,只有1了才去执行代码。这样就保证了只有最外层的事务有效,里面写的再多其实都没用。

        如果PHP-A没有结束事务,PHP-B没有写事务,但是有一些update和insert,PHP-C是正常的事务开始,事务提交。因为PHP-C的事务开始代码,tp根据$this->transTimes已经大于1来判断,实际是没运行任何代码,PHP-C的事务提交代码,同理也是没运行任何代码。一个mysql线程可能会用几个小时(我通过rds的日志看的,是真实情况),这样各种代码不断的累积,指不定那句代码就写了个事务回滚,这样之前所有的sql就都回滚了(真实经历,当出现这个问题是完全是懵了)。

        到这里其实是有个问题的,$this->transTimes是个变量,怎么可能2次PHP线程,这个东西会累加。我一开始也觉得不可能,这个道理如果能成立,代码岂不是都乱套了。但是实际测试确实如此。所以猜测tp不是单纯的把mysql的link存起来,而是把自己写的PDO类存起来,PDO类里包含了mysql连接和这个transTimes,所以就出现了累加。

        到这里还有个问题,如果多个PHP线程是公用一个mysql线程,如果PHP-A线程假如运行10秒,每秒插入一条数据,PHP-B在第五秒开始运行,写了句回滚,岂不是就把PHP-A的影响了。这个问题看起来是这么回事儿,但是实际应该不是,否则进程池不是扯淡了吗,实际测试了一下,发现是互不影响的,通过RDS查看2次的mysql线程号,也不是一个。猜测是swoole内部做了判断,并发的PHP进程,不共用一个mysql池。

        原理应该都说完了,优化改动就比较简单了,先在PDOConnection.php增加2个方法,可以获取transTimes和设置transTimes,找个中间件,在代码最开始检测下,如果大于1,就提交下事务。

if(Db::transTimesGet()!=0){
    Db::transTimesSet(1);
    Db::commit();
}

 在开启事务的代码那里,多加个判断,防止transTimes记录错误,造成事务二次开启

public function startTrans(): void
{
    try {
        $this->initConnect(true);

        ++$this->transTimes;
        if (1 == $this->transTimes) {
            try {
                $this->linkID->beginTransaction();
            }catch (\Exception $e){
                //如果事务产生嵌套,开启事务会报错,捕捉到错误后,先提交commit
                if($e->getMessage()=='There is already an active transaction'){
                    $this->commit();
                    $this->linkID->beginTransaction();
                }
            }

        } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
            $this->linkID->exec(
                $this->parseSavepoint('trans' . $this->transTimes)
            );
        }
        $this->reConnectTimes = 0;
    } catch (\Throwable | \Exception $e) {
        if ($this->transTimes === 1 && $this->reConnectTimes < 4 && $this->isBreak($e)) {
            --$this->transTimes;
            ++$this->reConnectTimes;
            $this->close()->startTrans();
        } else {
            if ($this->isBreak($e)) {
                // 尝试对事务计数进行重置
                $this->transTimes = 0;
            }
            throw $e;
        }
    }
}

        我说的这些,都是在关闭事务嵌套的前提下,个人觉得事务嵌套的应用场景几乎没有,所以干脆不要了。

protected function supportSavepoint(): bool
{
    //原来是true,我改成了false
    return false;
}

        最后说点感慨,这个问题简直太操蛋了,最开始呈现出来的现象,就是数据无缘无故的没了,没有任何规律,出现于任何表,单纯看那个控制器,没有任何bug。通过rds的日志,看几万条sql,才发现是这个现象。各种教程光说公用进程池,进程池怎么好,就没有看见一个人提过事务的问题,我自己总结出来的这些也不知道对不对,只是我从现实情况来看,确实是这样。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值