Yii2 解决2006 MySQL server has gone away问题

Yii2 解决2006 MySQL server has gone away问题

Yii2版本 2.0.15.1

php后台任务经常包含多段sql,如果php脚本执行时间较长,或者sql执行时间较长,经常会碰到mysql断连,报2006 MySQL server has gone away错误。通常,mysql断连了,重连数据库就好了,但是在哪里执行重连呢?这是一个值得思考的问题。

手动重连

最直接的解决办法,是在执行较长sql,或者脚本执行合适的时机,手动重连

\Yii::$app->db->close();
\Yii::$app->db->open();

这里有几个问题

  1. sql执行时间不好判断,容易受数据库压力的影响。
  2. 插入重连代码的时机不好判断,太频繁的重连会影响性能。
  3. 尽管已经充分考虑到插入重连数据库代码的位置,但是依然有"失手"的可能,不能保证完全解决问题。
  4. 每个数据库都需要充分考虑,例如\Yii::$app->db1->close(),代码可复用性不高。

需要时重连

捕获mysql断连异常,在异常处理中重连数据库,重新执行sql。

通常,使用php原生的PDO类连接数据库的操作步骤是

// 1. 连接数据库
$pdo = new PDO();

// 2. 执行prepare
$stm = $pdo->prepare(sql);

// 3. 绑定参数
$stm->bindValue();

// 4. 执行
$stm->query();
$stm->exec();

在Yii2框架中执行sql,通常有两种方式

  1. 使用ActiveRecord
$user = new app\models\User();
$user->name = ‘name‘;
$user->update();
  1. 拼sql
// 查询类sql select
$sql = <<<EOL
select * from user where name = ‘:name‘ limit 1;
EOL;
\Yii::$app->db->createCommand($sql, [‘:name‘ => ‘name‘])->queryAll();

// 更新类sql insert, update, delete...
$sql = <<<EOL
update xm_user set name = ‘name1‘ where name = ‘:name‘;
EOL;
\Yii::$app->db->createCommand($sql, [‘:name‘ => ‘name‘])->execute();

在Yii2中,sql的执行,都会调用yii\db\Connection类的createCommand()方法获得yii\db\Command实例。由yii\db\Command类的queryInternal()方法执行查询类sql,execute()方法执行更新类sql。

这里的yii\db\Connection类似于PDO类,代表数据库连接, yii\db\Command类类似于PDOStatement类, 它的$pdoStatement属性,保存生成的PDOStatement句柄。

于是我们改写这两个方法,实现捕获mysql断连异常,重连数据库。

use yii\db\Command;

class MysqlCommand extends Command
{
    public function __construct($config = [])
    {
        parent::__construct($config);
    }

    protected function queryInternal($method, $fetchMode = null)
    {
        try {
            return parent::queryInternal($method, $fetchMode);
        } catch (\yii\db\Exception $e) {
            if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
                echo ‘重连数据库‘;
                $this->db->close();
                $this->db->open();
                $this->pdoStatement = null;
                return parent::queryInternal($method, $fetchMode);
            }
            throw $e;
        }
    }

    public function execute()
    {
        try {
            return parent::execute();
        } catch (\yii\db\Exception $e) {
            if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
                echo ‘重连数据库‘;
                $this->db->close();
                $this->db->open();
                $this->pdoStatement = null;
                return parent::execute();
            }
            throw $e;
        }
    }
}

$this->pdoStatement = null是必要的,否则即使重连了数据库,这里再次执行queryInternal()execute()时,仍会使用原来生成的PDOStatement句柄,还是会报错。

yii\db\Exception是Yii实现的Mysql异常,帮我们解析了Mysql抛出的异常码和异常信息, 20062013均是Mysql断连异常码。

捕获到mysql异常后执行$this->db->close(),这里的$db是使用createCommand()方法传入的db实例, 所以我们也无需要判断db实例是哪一个。

如何使得在调用createCommand()方法的时候,生成的使我们重写的子类MysqlCommand而不是默认的yii\db\Command呢?

阅读代码

public function createCommand($sql = null, $params = [])
{
    $driver = $this->getDriverName();
    $config = [‘class‘ => ‘yii\db\Command‘];
    if ($this->commandClass !== $config[‘class‘]) {
        $config[‘class‘] = $this->commandClass; // commandClass属性能覆盖默认的yii\db\Command类
    } elseif (isset($this->commandMap[$driver])) {
        $config = !is_array($this->commandMap[$driver]) ? [‘class‘ => $this->commandMap[$driver]] : $this->commandMap[$driver];
    }
    $config[‘db‘] = $this;
    $config[‘sql‘] = $sql;
    /** @var Command $command */
    $command = Yii::createObject($config);
    return $command->bindValues($params);
}

我们发现,只要修改yii\db\ConnectioncommmandClass属性就能修改创建的Command类。

db.php配置中加上

‘db‘ => [
    ‘class‘ => ‘yii\db\Connection‘,
    ‘commandClass‘ => ‘path\to\MysqlCommand‘, // 加上这一条配置
    ‘dsn‘ => ‘‘,
    ‘username‘ => ‘‘,
    ‘password‘ => ‘‘,
    ‘charset‘ => ‘utf8‘,
],

这样的配置,要保证使用Yii2提供的\Yii::createObject()方法创建对象才能生效。

做完以上的修改,在执行拼sql类的查询且不绑定参数时没有问题,但是在使用ActiveRecord类的方法或者有参数绑定时会报错

SQLSTATE[HY093]: Invalid parameter number: no parameters were bound

说明我们的sql没有绑定参数。

为什么会出现这个问题?

仔细阅读yii\db\CommandqueryInternal()execute()方法,发现他们都需要执行prepare()方法获取PDOStatement实例, 调用bindPendingParams()方法绑定参数。

public function prepare($forRead = null)
{
    if ($this->pdoStatement) {
        $this->bindPendingParams(); // 绑定参数
        return;
    }

    $sql = $this->getSql();

    if ($this->db->getTransaction()) {
        // master is in a transaction. use the same connection.
        $forRead = false;
    }
    if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
        $pdo = $this->db->getSlavePdo();
    } else {
        $pdo = $this->db->getMasterPdo();
    }

    try {
        $this->pdoStatement = $pdo->prepare($sql);
        $this->bindPendingParams(); // 绑定参数
    } catch (\Exception $e) {
        $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
        throw new Exception($message, $errorInfo, (int) $e->getCode(), $e);
    }
}

protected function bindPendingParams()
{
    foreach ($this->_pendingParams as $name => $value) {
        $this->pdoStatement->bindValue($name, $value[0], $value[1]);
    }
    $this->_pendingParams = []; // 调用一次之后就被置空了
}

这里的$this->_pendingParams是在调用createCommand()方法时传入的。

但是调用一次之后,执行了$this->_pendingParams = []将改属性置空,所以当我们重连数据库之后,再执行到绑定参数这一步时,参数为空,所以报错。

本着软件开发的"开闭原则",对扩展开发,对修改关闭,我们应该重写一个子类,修改掉这个方法,但是这个方法是private的,所以只能注释掉该语句了。

总结

  1. 重写yii\db\Command类的queryInternal()execute()方法,捕获mysql断连异常。
  2. db.php中增加commandClass配置,使得生成的Command类为我们重写的子类。
  3. 注释掉yii\db\ConnectionbindPendingParams()方法的$this->_pendingParams = []语句,保证重新执行时可以再次绑定参数。

原文地址:https://www.cnblogs.com/xieyong-p/p/9808246.html

时间: 2024-08-30 14:35:38

Yii2 解决2006 MySQL server has gone away问题的相关文章

ThinkPHP出现General error: 2006 MySQL server has gone away的解决方法

错误: #13 {main}SQLSTATE[HY000]: General error: 2006 MySQL server has gone awayFILE: \ThinkPHP\Library\Think\Db\Driver.class.php(169) 原因分析: 本次错误提示是在cli模式运行,隔一段时间就会出现,查询资料后发现mysql默认没隔8个小时(2880000秒)就会断开 解决方案,解决方式找了三个 方法1 配置mysql.cnf(windows系统则是my.ini),指定

SQLyog恢复数据库报错解决方法【Error Code: 2006 - MySQL server has gone away】

https://blog.csdn.net/niqinwen/article/details/8693044 导入数据库的时候 SQLyog 报错了 Error Code: 2006 – MySQL server has gone away 搜了下,说是max_allowed_packet (MySQL的一个参数)设置的值不够大. 那我改下就行了 嘿嘿 In Windows: In the MySQL server installation directory,in my.ini file, a

(2006, ‘MySQL server has gone away‘)

django 中,导入excel到mysql中.excel 转成sql,然后导入mysql. sql过大,出现(2006, 'MySQL server has gone away')错误. 改mysql 配置my.cnf,[mysqld]中的 max_allowed_packet = 64M ait_timeout = 86400 解决.

[Err] 2006 - MySQL server has gone away

1.错误描述 [Err] 2006 - MySQL server has gone away 2.错误原因 在将数据库脚本利用MySQL客户端导入时,出现这个错误:结果查明,由于脚本中的insert语句过多,插入数据量过大,导致MySQL客户端和服务器连接断开 3.解决办法 (1)修改MySQL配置文件my.ini 设置max_allowed_packed参数 (2)查看MySQL连接是否超时 (3)查看服务是否中断 版权声明:本文为博主原创文章,未经博主允许不得转载.

MySQL导入sql脚本错误:2006 - MySQL server has gone away

下面我给各位同学介绍在mysql中导入数据库时提示MySQL server has gone away问题的解决方法,如果你碰到mysql提示MySQL server has gone away错误我们可进入参考. 到如一些小脚本很少报错,但最近导入一个10+M的SQL脚本,却重复报错: Error occured at:2014-03-24 11:42:24Line no.:85Error Code: 2006 - MySQL server has gone away 最终找到原因,原来是My

#2006 - MySQL server has gone away

#2006 - MySQL server has gone away 对于web应用来说,经常会用到mysql,而数据的备份与还原是web应用经常做的事,一般来说,用客户端工具phpmyadmin,sqlyog 等,来备份数据库没有任何问题,还原时经常碰到mysql提示的#2006错误. Error: 2006 (CR_SERVER_GONE_ERROR) Message: MySQL server has gone away 原因可能是sql语句过长,超过mysql通信缓存区最大长度: 调整m

mysql error: (2006, &#39;MySQL server has gone away&#39;)

max_allowed_packet=16M wait_timeout=400000 interactive_timeout = 400000 vim /etc/my.cnf  mysqld 中加入上面的内容. mysql error: (2006, 'MySQL server has gone away')

[django1.6]跑批任务错误(2006, &#39;MySQL server has gone away&#39;)

有个django的定时任务,调用django的orm来对数据库进行数据处理. 在交互环境下直接启动pyhton脚本没有问题,放在定时任务中时候,总是出现 (2006, 'MySQL server has gone away') 的错误,开始以为是定时框架外部调用的问题,但是后来想想也不合理,为啥直接在shell中调用就没错呢, 想到django1.6的一些数据库连接的新属性(例如持久化连接等)会不会有影响,于是google了下. 看到django官网上有人提过这个类似于bug的东西: https

mysql导入大批量数据时,出现ERROR : (2006, &#39;MySQL server has gone away&#39;)

mysql导入数据时,出现ERROR : (2006, 'MySQL server has gone away') 解决方案:官方解释是适当增大 max_allowed_packet 参数可以使client端到server端传递大数据时,系统能够分配更多的扩展内存来处理. 设置 max_allowed_packet = 256M (1024*1024*256) set global max_allowed_packet=268435456; 使用set global命令修改 max_allowe