Yii2版本 2.0.15.1php
php後臺任務常常包含多段sql,若是php腳本執行時間較長,或者sql執行時間較長,常常會碰到mysql斷連,報2006 MySQL server has gone away
錯誤。一般,mysql斷連了,重連數據庫就行了,可是在哪裏執行重連呢?這是一個值得思考的問題。mysql
最直接的解決辦法,是在執行較長sql,或者腳本執行合適的時機,手動重連sql
\Yii::$app->db->close(); \Yii::$app->db->open();
這裏有幾個問題數據庫
\Yii::$app->db1->close()
,代碼可複用性不高。捕獲mysql斷連異常,在異常處理中重連數據庫,從新執行sql。yii2
一般,使用php原生的PDO
類鏈接數據庫的操做步驟是app
// 1. 鏈接數據庫 $pdo = new PDO(); // 2. 執行prepare $stm = $pdo->prepare(sql); // 3. 綁定參數 $stm->bindValue(); // 4. 執行 $stm->query(); $stm->exec();
在Yii2框架中執行sql,一般有兩種方式框架
ActiveRecord
$user = new app\models\User(); $user->name = 'name'; $user->update();
// 查詢類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
句柄。yii
因而咱們改寫這兩個方法,實現捕獲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
句柄,仍是會報錯。fetch
yii\db\Exception
是Yii實現的Mysql異常,幫咱們解析了Mysql拋出的異常碼和異常信息, 2006
和2013
均是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\Connection
的commmandClass
屬性就能修改建立的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\Command
的queryInternal()
和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
的,因此只能註釋掉該語句了。
yii\db\Command
類的queryInternal()
和execute()
方法,捕獲mysql斷連異常。db.php
中增長commandClass
配置,使得生成的Command
類爲咱們重寫的子類。yii\db\Connection
中bindPendingParams()
方法的$this->_pendingParams = []
語句,保證從新執行時能夠再次綁定參數。