报错信息
Query error: MySQL server has gone away - Invalid query:xxxx
环境
- php 5.6 (CI框架)
- mysql 5.6
场景
一个定时任务(crontab 脚本),结构体方法先加载了model类,即进行了连接。插入数据库之前有一段很耗时的代码在执行,当执行插入语句时,报错。
代码剖析(伪代码)
<?php
/**
* 这是一个脚本
*/
ini_set('memory_limit','-1'); //升级为256M内存
class Test extends BASE_Controller{
public function __construct(){
parent::__construct();
$this->load->model('Test_model','test_model');
}
public function index(){
//这里要执行很长很长时间的处理,时间可能达到了5分钟还没有处理完
longTimeFunc();
// ... ...
//插入数据
$res = $this->ocpx_model->insert_ignore($insert_data);
}
}
分析原因
查看数据库connect_timeout设置
show variables like '%timeout%';
+----------------------------+----------+
| Variable_name | Value |
+----------------------------+----------+
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| innodb_lock_wait_timeout | 20 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 30 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| slave_net_timeout | 3600 |
| wait_timeout | 30 |
+----------------------------+----------+
很显然,connect_timeout设置为10s,反观我们的代码,初始化时连接了数据库,连接池存储了当前连接,而代码执行到index方法调用longTimeFunc()方法耗时很长,这时候mysql主动断开了连接。然而我们还是继续使用了连接池里旧连接,因此报错MySQL server has gone away
解决方法
方法一
修改connect_timeout值,可以修改长一点,但是治标不治本。
方法二
关闭当前连接,再进行初始化
$this->master->close();
$this->master->initialize();
以下是CI框架DB_driver.php文件关于initialize方法的封装:
/**
* Initialize Database Settings
*
* @return bool
*/
public function initialize()
{
/* If an established connection is available, then there's
* no need to connect and select the database.
*
* Depending on the database driver, conn_id can be either
* boolean TRUE, a resource or an object.
*/
if ($this->conn_id)
{
return TRUE;
}
// ----------------------------------------------------------------
// Connect to the database and set the connection ID
$this->conn_id = $this->db_connect($this->pconnect);
// No connection resource? Check if there is a failover else throw an error
if ( ! $this->conn_id)
{
// Check if there is a failover set
if ( ! empty($this->failover) && is_array($this->failover))
{
// Go over all the failovers
foreach ($this->failover as $failover)
{
// Replace the current settings with those of the failover
foreach ($failover as $key => $val)
{
$this->$key = $val;
}
// Try to connect
$this->conn_id = $this->db_connect($this->pconnect);
// If a connection is made break the foreach loop
if ($this->conn_id)
{
break;
}
}
}
// We still don't have a connection?
if ( ! $this->conn_id)
{
log_message('error', 'Unable to connect to the database');
if ($this->db_debug)
{
$this->display_error('db_unable_to_connect');
}
return FALSE;
}
}
// Now we set the character set and that's all
return $this->db_set_charset($this->char_set);
}
方法三
以下是CI官方手册内容:“当你在处理一些重量级的 PHP 操作时(例如处理图像),若超过了数据库的超时值,你应该考虑在执行后续查询前先调用 reconnect() 方法向数据库发送 ping 命令,这样可以优雅的保持连接有效或重新建立起连接。”
参考https://codeigniter.org.cn/user_guide/database/connecting.html
【亲测,并未生效】
其实在实践过程中,我们会发现,这个reconnect函数并没有生效,调用数据库语句之前,如果超过连接时间,依然会报mysql has gone away。
【有坑】
进一步探索才发现,有坑,参考https://www.cnblogs.com/joeblackzqq/p/5614948.html。
大概意思就是实际上reconnect方法还依赖于MYSQL_OPT_RECONNECT这个配置,而这个配置默认(自mysql5.0.3开始)是关闭的!
【解决办法】
解决办法是调用 mysql_options ,将MYSQL_OPT_RECONNECT设置为1:
char value = 1;
mysql_options(mysql, MYSQL_OPT_RECONNECT, &value);
相关知识
mysql 几个超时参数(timeout)解释
1.connect_timeout
mysql客户端在尝试与mysql服务器建立连接时,mysql服务器返回错误握手协议前等待客户端数据包的最大时限。默认10秒。
2.interactive_timeout / wait_timeout
mysql关闭交互/非交互连接前等待的最大时限。默认28800秒。
3.lock_wait_timeout
sql语句请求元数据锁的最长等待时间,默认为一年。此锁超时对于隐式访问Mysql库中系统表的sql语句无效,但是对于使用select,update语句直接访问mysql库中标的sql语句有效
4.net_read_timeout / net_write_timeout
mysql服务器端等待从客户端读取数据 / 向客户端写入数据的最大时限,默认30秒。
5.slave_net_timeout
mysql从复制连结等待读取数据的最大时限,默认3600秒。