天天看点

Yii2实现mysql断线重连

在实际生产环境中,我们有时候需要创建控制台任务在后台执行一系列工作任务,但部分任务耗时较长,此时就会产生一个情况,mysql连接超时,那么我们怎么来控制mysql不连接超时呢?

思路:

每次在执行sql的时候先判定一次当前mysql连接对象是否过期?如果没有过期则继续执行,过期了初始化一个新的mysql连接来继续执行。

限制

尽量不要使用事务,对需要事务处理的任务,要谨慎处理(一旦在处理事务的时候mysql连接对象断连,那么就会造成不可挽回的随时,事务进行了回滚,不管你执行commit 还是rollback 都会报错)

怎么在yii2的console 控制台中使用并判定断线重连呢?

一、重写\yii\db\Command类中的execute 与 queryInternal方法

<?php
    namespace common\components;

    use Yii;

    /**
     * 新增加执行sql时断开重连
     * 数据库连接断开异常
     * errorInfo = [''HY000',2006,'错误信息']
     * Class Command
     * @package common\components
     */
    class Command extends \yii\db\Command
    {
        const EVENT_DISCONNECT = 'disconnect';

        /**
         * 处理修改类型sql的断线重连问题
         * @return int
         * @throws \Exception
         * @throws \yii\db\Exception
         */
        public function execute()
        {
            try{
                return parent::execute();
            }catch(\Exception $e){
                if($this->handleException($e))
                    return parent::execute();
                throw $e;
            }
        }

        /**
         * 处理查询类sql断线重连问题
         * @param string $method
         * @param null $fetchMode
         * @return mixed
         * @throws \Exception
         * @throws \yii\db\Exception
         */
        protected function queryInternal($method, $fetchMode = null)
        {
            try{
                return parent::queryInternal($method, $fetchMode = null);
            }catch(\Exception $e){
                if($this->handleException($e))
                    return parent::queryInternal($method, $fetchMode);
                throw $e;
            }
        }

        /**
         * 处理执行sql时捕获的异常信息
         * 并且根据异常信息来决定是否需要重新连接数据库
         * @param \Exception $e
         * @return bool true: 需要重新执行sql false: 不需要重新执行sql
         */
        private function handleException(\Exception $e)
        {
            //如果不是yii\db\Exception异常抛出该异常或者不是MySQL server has gone away
            $offset = stripos($e->getMessage(),'MySQL server has gone away');
            if(($e instanceof \yii\db\Exception) == false OR $offset === false)
                //OR $e->errorInfo[0] != 'HY000' OR $e->errorInfo[1] != 2006)
                return false;

            $this->trigger(static::EVENT_DISCONNECT);

            //将pdo设置从null
            $this->pdoStatement = NULL;
            $this->db->resetPdo();
            //$this->db->close();
            return true;
        }
    }
           

二、\yii\db\Connection类中新增加方法 resetPdo

/**
     * 重置连接信息
     */
    public function resetPdo()
    {
        $this->_master = false;
        $this->_slave = false;
        $this->pdo = NULL;
    }
           

三、使用

<?php
    return [
        'components' => [
            'db' => [
                'class'   => 'yii\db\Connection',
                'commandClass' => 'common\components\Command',
                'username' => 'XXX',
                'password' => 'XXX',
                'dsn' => 'mysql:host=XXX;dbname=XXX;port=3306',
            ],
        ],
    ];
           

后话

修改中遇到的问题:

眼尖的同学可能已经看到了在上面的"handleException"函数中有段注释了的代码"OR $e->errorInfo[0] != 'HY000' OR  $e->errorInfo[1] != 2006" 被"$offset = stripos($e->getMessage(),'MySQL server has gone away');" 代替了。那是因为在断线的情况下,客户端首次请求时会产生一个"Error"与一个"PDOException"。由于Yii2在底层实现了一个错误处理函数。在捕获到错误后会转换成一个"ErrorException"导致"PDOException"被覆盖了。同时在"PDOException"中的错误状态码也获取不到了。如果你有更好的方案也希望您能在下面的留言中一起交流
           

原文链接