> 项目开发时使用 PHP 守护进程连接 Mysql 的时候,总是报错,错误信息如下: > `MySQL server has gone away` `Error reading result set's header.` ### 报错原因 `MySQL server has gone away` 错误是指 `Client` 端 和 `MySQL Server` 之间的链接断开了。 ### 原因查询 1. MySQL 服务宕 - 使用sql查询 `show global status like 'uptime'`; 如果 uptime 数值很大,表明 mysql 服务运行了很久近服务没有重启过。 - 查询 Mysql 的错误日志,看看有没有重启的信息。 2. mysql 请求链接进程被主动 kill - 这种属于人为操作,使用 `show global status like 'com_kill';` 查询 3. SQL语句太长 `Your SQL statement was too large.` - 应用程序长时间的执行批量的MYSQL语句。 - 执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。 - 当查询的结果集超过 `max_allowed_packet` 也会出现这样的报错,可以打出相关报错的语句。 - 用 `select * into outfile` 的方式导出到文件,查看文件大小是否超过 max_allowed_packet ,如果超过则需要调整参数,或者优化语句。 - 查询参数:`show global variables like 'max_allowed_packet';` - 修改参数:`set global max_allowed_packet=1024*1024*16;` `show global variables like 'max_allowed_packet';` - 修改文件:修改my.cnf,加大max_allowed_packet的值即可。 4. mysql 连接超时 - 即某个 mysql 长连接很久没有新的请求发起,达到了 server 端的 timeout,被 server 强行关闭。 此后再通过这个 connection 发起查询的时候,就会报错 `server has gone away`。 - 查询参数: `show global variables like '%timeout';` wait_timeout 结果值即mysql链接在无操作多少秒后被自动关闭。 ### 分析 经过分析,并没有大的sql执行,也没有宕机,也没有主动kill过,查询 `wait_timeout` 后发现应该是超时导致; #### 原因 程序中获取数据库连接时采用了 Singleton 的做法,虽然多次连接数据库,但其实使用的都是同一个连接,而且程序中某两次操作数据库的间隔时间超过了 wait_timeout(SHOW STATUS能看到此设置),那么就可能出现问题。 ### 解决方案 - 在my.cnf文件中添加或者修改以下两个变量: - `wait_timeout=2880000` - `interactive_timeout = 2880000` - 连接数据库时进行设置 - `set [global] interactive_timeout=24*3600` 带global是全局设置,否则只有本次生效 - `set [global] wait_timeout=24*3600` 带global是全局设置,否则只有本次生效 - 检查 MySQL的链接状态,使其重新链接 - `mysql_ping` 使用此方法检测 - 每次链接后,主动关闭 connection 但是这样,就起不到 Singleton 类的效果了 ### 我的方案 #### 1、编写测试代码,测试是否因为超时导致的此问题 ##### 方案一 ```php function querySql() { $oDB = DBz::getInstance('Apppush'); $sQuery = 'UPDATE test_tables SET start_count = start_count+1 WHERE id = 100'; $oDB->exec($sQuery); } $i = 10; while($i > 0) { querySql(); if($i == 5) { Log::info('============开始睡眠============'); sleep(1300); // 睡眠时间大于 wait_timeout Log::info('============结束睡眠============'); } $i--; } ``` 执行 php 文件,等待执行完毕后,查看错误信息,是否报错,以及查看 start_count 执行次数是否到达10次。 ##### 方案二 ```php function querySql() { $oDB = DBz::getInstance('Apppush'); $sQuery = 'UPDATE test_tables SET start_count = start_count+1 WHERE id = 100'; $oDB->exec($sQuery); } function waitTimeout() { $oDB = DBz::getInstance('Apppush'); $oDB->prepares('SET SESSION wait_timeout = 1'); } for ($i = 1; $i<=10; $i++) { querySql(); sleep(2); if($i%3 == 1) { waitTimeout(); } } ``` 执行 php 文件,中间设置连接超时,下一次继续操作时,应该会报错 #### 解决方法 因为如果主动释放连接资源,会导致链接数增加,不建议这样做。 编写 pdo 的模拟 `mysql_ping` 方法 ```php new PDO时配置异常设置,错误报告 抛出 EXCEPTION 异常 $connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); function pdoPing($dbconn) { try { $ret = $dbconn->getAttribute(constant("PDO::ATTR_SERVER_INFO")); if ($ret === null) { return false; } } catch (\PDOException $e) { if(strpos($e->getMessage(), 'MySQL server has gone away')!==false) { return false; } } return true; } ```