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 = []语句,保证重新执行时可以再次绑定参数。

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

  1. MySQL(Navicat)运行.sql文件时报错:[Err] 2006 - MySQL server has gone away 的解决方法

    背景: 今天导入一个数据量很大的.sql文件时,报错: 原因: 可能是sql语句过长,超过mysql通信缓存区最大长度. 解决:1. 编辑 MySQL 安装目录下的 my.ini,在最后添加以下内容: ...

  2. flask+mako+peewee(下)(解决了Error 2006: MySQL server has gone away)

    这篇主要介绍在这次项目中使用的peewee 文档地址:http://peewee.readthedocs.org/en/latest/index.html 首先我们要初始化一个数据库连接对象.这里我使 ...

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

    错误: #13 {main}SQLSTATE[HY000]: General error: 2006 MySQL server has gone awayFILE: \ThinkPHP\Library ...

  4. Django (2006, 'MySQL server has gone away') 本地重现与解决

    最近我们的Django项目供Java Sofa应用进行tr调用时, 经常会出现一个异常: django.db.utils.OperationalError: (2006, 'MySQL server ...

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

    https://blog.csdn.net/niqinwen/article/details/8693044 导入数据库的时候 SQLyog 报错了 Error Code: 2006 – MySQL ...

  6. #2006 - MySQL server has gone away 问题解决方法 (全) (转)

    #2006 - MySQL server has gone away 问题解决方法 原文地址:http://www.cnblogs.com/bisonjob/archive/2009/08/18/15 ...

  7. MYSQL导入数据报错|MYSQL导入超大文件报错|MYSQL导入大数据库报错:2006 - MySQL server has gone away

    导SQL数据库结构+数据时,如果数据是批量插入的话会报错:2006 - MySQL server has gone away. 解决办法:找到你的mysql目录下的my.ini配置文件(如果安装目录没 ...

  8. 2006 - MySQL server has gone away

    mysql出现ERROR : (2006, 'MySQL server has gone away') 的问题意思就是指client和MySQL server之间的链接断开了. 造成这样的原因一般是s ...

  9. mysql 导入数据是报错:2006 - MySQL server has gone away

    导SQL数据库结构+数据时,如果数据是批量插入的话会报错:2006 - MySQL server has gone away. 解决办法:找到你的mysql目录下的my.ini配置文件,加入以下代码 ...

随机推荐

  1. springboot集成schedule(深度理解)

    背景 在项目开发过程中,我们经常需要执行具有周期性的任务.通过定时任务可以很好的帮助我们实现. 我们拿常用的几种定时任务框架做一个比较: 从以上表格可以看出,Spring Schedule框架功能完善 ...

  2. pymysql安装

    安装python3之后 链接数据库需要安装pymysql pymysql 下载地址 https://pypi.python.org/pypi/PyMySQL3/0.5 ,下载之后传到linux虚拟机, ...

  3. 在线OJ的小demo

    牛课网OJ规则 用readLine()代替read_line() 用readLine()代替read_line() 用readLine()代替read_line() 用readLine()代替read ...

  4. JavaScript设计模式 Item 4 --继承

    1.继承 在javascript中继承是一个非常复杂的话题,比其他任何面向对象语言的中的继承都复杂得多.在大多数其他面向对象语言中,继承一个类只需要使用一个关键字即可.与它们不同,在javascrip ...

  5. python如何优雅的打飞机

    这是一个打飞机的游戏,结构如下:     其中images中包含的素材为   命名为alien.png   命名为ship.png 游戏效果运行是这样的:     敌军,也就是体型稍微大点的,在上方左 ...

  6. driver匹配元素定位用法大全

    # -*- coding:utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By fr ...

  7. MySQL 8 新特性之降序索引

    MySQL 8.0终于支持降序索引了.其实,从语法上,MySQL 4就支持了,但正如官方文档所言,"they are parsed but ignored",实际创建的还是升序索引 ...

  8. 【强连通分量+spfa】Bzoj1179 Apio2009 Atm

    Description Solution 显然缩强连通分量,然后求最长路,虽然是DAG但还是有点麻烦,于是用了spfa. Code 重建图_数组写错好多次,感觉做这题也就是练了一下实现. #inclu ...

  9. UR机械臂运动学正逆解方法

    最近几个月因为工作接触到了机械臂的项目,突然对机械臂运动方法产生了兴趣,也就是如何控制机械臂的位置和姿态.借用一张网上的图片,应该是ur5的尺寸.我用到的是ur3机械臂,除了尺寸不一样,各关节结构和初 ...

  10. c# 基于文件系统实现的队列处理类

    现实业务中经常遇到需要队列处理的问题. 问题场景: 客户端记录设备运行数据,传输给服务器.在传输中可能存在延迟或中断情况.当中断时,系统传输数据可能因为无法传输或电脑重启,会导致服务器数据记录不连续. ...