Yii 數據庫重連告別General error: 2006 MySQL server has gone away

General error: 2006 MySQL server has gone away

  • 錯誤緣由
  • 製造錯誤
  • 解決辦法
  • 最新辦法

錯誤緣由

Mysql has gone awayphp

  1. MySQL 服務宕了
  2. mysql鏈接超時 show global variables like ‘%timeout’;
  3. mysql請求連接進程被主動kill show global status like ‘com_kill’;
  4. Your SQL statement was too large. show global variables like ‘max_allowed_packet’;

製造錯誤

造一個表java

-- ----------------------------
-- Table structure for t_biubiu
-- ----------------------------
DROP TABLE IF EXISTS `t_biubiu`;
CREATE TABLE `t_biubiu` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `value` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

而後造一個程序mysql

public function actionInsert(){
        Biubiu::model()->findAll();
        $result = Yii::app()->db->createCommand("show global variables like '%timeout'")->queryAll();
        self::show_vars($result);
        for($index = 1 ;$index< 10;$index++){
            $model_ = new Biubiu();
            $model_->value = "insert".$index;
            if($index == 8){
                sleep(31);
            }
            $model_->save();
        }
        $model = new Biubiu();
        $model->value = "insert4";
        $model->save();
     }

設置你的MYSQL的wait_timeout = 30,interactive_timeout也要設置,不然不生效sql

執行的時候就會拋異常:數據庫

exception 'CDbException' with message 'CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away. The SQL statement executed was: INSERT INTO `t_biubiu` (`value`) VALUES (:yp0). Bound with :yp0='insert8'' in E:\phpWorks\framework\db\CDbCommand.php:362

sleep有效果了。併發

大部分狀況就是超時致使的,尤爲是腳本執行時間太長app

解決辦法

若是運維不一樣意 改變wait_timeout的值:框架

方法 作法
1 wait_timeout改大一些,並不能一勞永逸
2 代碼中遇到超時重連
3 檢查是否是Mysql鏈接過多,併發過高,忘記釋放鏈接

解決方法推薦

例如,讀寫分離的 JDbConnection。在調用save delete方法的時候執行ensureActive() 。注意 @,不然異常發生的時候PHP ERROR直接掛掉。運維

<?php

/** * JDbConnection(Database Connection Manager) class is a manager of database connections. * * for the purpose of database read/write splitting. * It override the createCommand method, * detect the sql statement to decide which connection will be used. * Default it use the master connection. * */
class JDbConnection extends CDbConnection
{
    /** * @var array $slaves.Slave database connection(Read) config array. * The array value's format is the same as CDbConnection. * <code> * 'components'=>array( * 'db'=>array( * 'class' => 'JDbConnection', * 'connectionString'=>'MySQL://<master>', * 'slaves'=>array( * array('connectionString'=>'mysql://<slave01>'), * array('connectionString'=>'mysql://<slave02>'), * ) * ) * ) * </code> */
    public $slaves = array();

    /** * Whether enable the slave database connection. * Defaut is true.Set this property to false for the purpose of only use the master database. * * @var bool $enableSlave */
    public $enableSlave = true;

    /** * @var CDbConnection */
    private $_slave;


    /** * Creates a CDbCommand object for excuting sql statement. * It will detect the sql statement's behavior. * While the sql is a simple read operation. * It will use a slave database connection to contruct a CDbCommand object. * Default it use current connection(master database). * * @override * @param string $sql * @return CDbCommand */
    public function createCommand($query = null)
    {
        if ($this->enableSlave && !$this->getCurrentTransaction() && self::isReadOperation($query)) {
            return $this->getSlave()->createCommand($query);
        } else {
            return parent::createCommand($query);
        }
    }


    /** * Construct a slave connection CDbConnection for read operation. * * @return CDbConnection */
    public function getSlave()
    {
        if (!isset($this->_slave)) {
            foreach ($this->slaves as $slaveConfig) {
                if (!isset($slaveConfig['class']))
                    $slaveConfig['class'] = 'CDbConnection';
                try {
                    if ($slave = Yii::createComponent($slaveConfig)) {
                        Yii::app()->setComponent('dbslave', $slave);
                        $this->_slave = $slave;
                        break;
                    }
                } catch (Exception $e) {
                    Yii::log('Create slave database connection failed!', 'warn');
                    continue;
                }
            }
            if (!$this->_slave) {
                $this->_slave = clone $this;
                $this->_slave->enableSlave = false;
            }
        }
        return $this->_slave;
    }


    /** * Detect whether the sql statement is just a simple read operation. * Read Operation means this sql will not change any thing ang aspect of the database. * Such as SELECT,DECRIBE,SHOW etc. * On the other hand:UPDATE,INSERT,DELETE is write operation. * * @return bool */
    public function isReadOperation($sql)
    {
        return !!preg_match('/^\s*(SELECT|SHOW|DESCRIBE|PRAGMA)/i', $sql);
    }


    /** * 確保數據庫鏈接有效 * * @params bool $isSlaveping主庫仍是從庫 * @return void */
    public function ensureActive($isSlave = true)
    {
            if ($this->getActive()) {
                try {
                    @$this->getPdoInstance()->query('SELECT 1');
                } catch (PDOException $e) {
                    $this->setActive(false);
                    $this->setActive(true);
                }
            }
    }
}

調用示例:yii

Yii::app()->db->ensureActive();

另一種方法

執行SQL以前執行:

Yii::$app->db->createCommand('SET SESSION wait_timeout = 28800;')->execute();

最新辦法

避免每次多請求了一次數據庫。只有在遇到問題時進入異常處理,重試的時候Yii會自動進行重連[不肯定]。

public static function retry($params, $call){
    for($retry = 0; $retry < 3;$retry++){
        try{
            if($params instanceof CModel){
                return $params->$call();
            }
        }catch(CDbException $e){
            if (!(strpos($e, "error: 2006") && !strpos($e, "error: 2013"))) {
                throw new Exception($e->getMessage(), 520);
            }
        }
    }
    return false;
}

2017年4月9日 更新

最近發現,公司所用的yii framework1.1的框架,和官方下載的不太同樣。

下載最新框架,測試代碼以下,我將mysql設置爲wait_timeout=30, interactive_timeout=30

class DbTestCommand extends CConsoleCommand {

    public function actionInsert(){

        $sql = "insert into adv_pro ( `name` , `int_value`) values ('insert', 100)";
        Yii::app()->localdb->createCommand($sql)->execute();
        sleep(31);
        $sql = "insert into adv_pro ( `name` , `int_value`) values ('insert', 200)";
        Yii::app()->localdb->createCommand($sql)->execute();

    }

}

結果就出現了:

PHP Error[2]: PDOStatement::execute(): MySQL server has gone away
    in file D:\work_project\framework\db\CDbCommand.php at line 336
#0 D:\work_project\framework\db\CDbCommand.php(336): PDOStatement->execute()
#1 D:\work_project\task\protected\commands\DbTestCommand.php(19): CDbCommand->execute()
#2 unknown(0): DbTestCommand->actionInsert()

再加上@符號抑制錯誤

@Yii::app()->localdb->createCommand($sql)->execute();

結果異常就拋出來了:

exception 'CDbException' with message 'CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away. The SQL statement executed was: insert into adv_pro ( `name` , `int_value`) values ('insert', 200)' in D:\work_project\framework\db\CDbCommand.php:363
Stack trace:
#0 D:\work_project\task\protected\commands\DbTestCommand.php(19): CDbCommand->execute()

那麼能夠把框架的db/CDbCommand.php內的execute方法改一下,改爲:

if($params===array())
    @$this->_statement->execute();
else
    @$this->_statement->execute($params);

這樣就能捕獲到這個異常了。

那麼據此,能夠爲CDbCommand.php增長一個方法:

public function sqlDbExecute($params=array()){
    $e = new Exception("sqlDbExecute failed three times", 10001);
    for ($count=0;$count<3;$count++){
        try {
            $result = $this->execute($params);
            return $result;
        } catch (Exception $e) {
               Yii::log('Error in sqlDbExecute SQL: '.$this->getText(),CLogger::LEVEL_ERROR,'system.db.CDbCommand');
            $this->_connection->setActive(true);
            //If not found 2006 then throw up
            if (!strpos($e, "error: 2006")) {
                   throw new Exception($e,10001);
            }
        }
    }
    throw new Exception($e,10001);

}
相關文章
相關標籤/搜索