在工作中,有时候一些后台脚本需要长时间的运行,同时可能在连接数据库后,长时间不与数据库服务端交互。此时,服务器可能会断开与客户端的连接。从而客户端再次交互时就会出现"MySQL server has gone away"连接丢失。 此次修改达到的效果:断线重连机制对应用层完全透明,无需自己重复发送请求。
消费进程中长时间消费不到数据,mysql设置的自动断开时间超过后,mysql自动断线,服务端报mysql gone away,这时需要捕获异常重连并重试。或者将长连接改为短连接也可以解决该问题。
解决
1 配置项新增
'db' => [
'class' => 'QttLib\Yii\Tracing\DBConnection',
'commandClass' => 'common\components\DbCommand',// 加上这一条配置,Yii2 解决2006 MySQL server has gone away问题
'dsn' => 'mysql:host=xxx;dbname=xx',
'username' => 'xx',
'password' => 'xxx',
'charset' => 'utf8',
'enableLogging' => true,
],
2?DbCommand.php文件内容:
<?php
namespace app\models\common;
use app\components\China;
/**
* 新增加执行sql时断开重连
* 数据库连接断开异常
* errorInfo = [''HY000',2006,'错误信息']
* Class Command
*/
class Command extends \yii\db\Command
{
public $retry;
/**
* 处理修改类型sql的断线重连问题
* @return int
* @throws \Exception
* @throws \yii\db\Exception
*/
public function execute()
{
try{
return parent::execute();
}catch(\Exception $e){
China::logger($e->getMessage().'|'.$e->errorInfo,['prefix' => 'script_time_out_execute']);
if($this->retryBecauseGongAway($e)) {
return parent::execute();
} else {
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);
}catch(\Exception $e){
China::logger($e->getMessage().'|'.json_encode($e->errorInfo,JSON_UNESCAPED_UNICODE),['prefix' => 'script_time_out_queryInternal']);
//Error while sending QUERY packet. PID=30822
if($this->retryBecauseGongAway($e)) {
return parent::queryInternal($method, $fetchMode);
} else {
throw $e;
}
}
}
/**
* 处理执行sql时捕获的异常信息
* 并且根据异常信息来决定是否需要重新连接数据库
* @param \Exception $e
* @return bool true: 需要重新执行sql false: 不需要重新执行sql
*/
private function retryBecauseGongAway(\Exception $e)
{
$condition1 = ($e instanceof \yii\db\Exception) && $e->errorInfo[0] == 'HY000' && $e->errorInfo[1] == '2006';
$condition2 = strpos($e->getMessage(),'Error while sending QUERY packet') !== false;
if ($condition1 || $condition2) {
$this->retry = true;
$this->pdoStatement = null;
$this->db->close();
$this->db->open();
return true;
}
return false;
}
/**
* 利用$this->retry属性,标记当前是否是数据库重连
* 重写bindPendingParams方法,当当前是数据库重连之后重试的时候
* 调用bindValues方法重新绑定一次参数.
*/
protected function bindPendingParams()
{
if ($this->retry) {
$this->retry = false;
$this->bindValues($this->params);
}
parent::bindPendingParams();
}
}