PHP 错误信息
errno:2 errmsg:mysqli_ping(): MySQL server has gone away
errno:8 errmsg:mysqli_ping(): send of 5 bytes failed with errno=32 Broken pipe
值 | 常量 | 说明 |
2 | E_WARNING | 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。 |
8 | E_NOTICE | 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。 |
原因分析
mysqli_ping()
方法是专门为 libmysql 这种旧式的数据库驱动而设计的,它跟现在新式的 mysqlnd 驱动已不再兼容,所以才会报错。
从 PHP 5.3.0 开始,就引入了 mysqlnd 驱动,并且在 5.4.0 版本开始作为默认的 MySQL 数据库驱动。
所以,现在最新的 PHP 版本也是使用 mysqlnd 作为默认驱动。
mysqli_ping()
方法的作用是检测当前 mysql 连接是否存活,若不存活则自动重连。不过官方有下面一段话
Note: The php.ini setting mysqli.reconnect is ignored by the mysqlnd driver, so automatic reconnection is never attempted.
这说明了对于 mysqlnd 驱动来说,mysqli_ping()
已经不能再实现自动重连了。
那我们还能够用该方法来判断当前的 MySQL 连接是否存活吗?如果能,那在我们实现单例模式时是有帮助的。
因为我只需要连接一次数据库,就可以自始至终都使用同一个数据库连接来操作数据库。
<?php
class DB
{
private static $link;
public static function getLink()
{
if(empty(self::$link) || mysqli_ping(self::$link) === false) {
self::$link = mysqli_connect("host", "username", "password", "dbname");
}
return self::$link;
}
}
看起来上面的程序并没有问题,但是为什么就会出现上面两个报错呢?而且是由于调用 mysqli_ping()
而引起的错误。
errno:2 errmsg:mysqli_ping(): MySQL server has gone away
errno:8 errmsg:mysqli_ping(): send of 5 bytes failed with errno=32 Broken pipe
其实无论怎么说,这个方法也不应该报错才对的,返回 true
或者 false
来告诉我们连接是否存活即可,这里直接报错不太好。
根据 Bug #52561 和 Bug #53287 这两个反馈来看,官方也没有给出答案,只是说 mysqli_ping()
已不适用于 mysqlnd,如果一定要用该函数那只能配合 libmysql 驱动来用。
原因结论
实际上,如果 MySQL 连接还存活的情况下,使用 mysqli_ping()
去检测是不会报错的,一切正常。只有当 MySQL 主动断开了与 PHP 的连接后,再用该方法去检测时才会出现报错信息。
这种情况一般会出现在两次间隔时间较长的时间内,期间 MySQL 根据配置参数 wait_timeout
的阀值而断开了长时间没有再发送查询请求的连接。若此时再调用 mysqli_ping(self::$link)
就会出现 MySQL server has gone away
这样的报错信息。
此外,对于第二个错误即 send of 5 bytes failed with errno=32 Broken pipe
虽然没有找到确切的原因,但是可以推测也是由于 MySQL 连接断开而造成的。因为这两个错误出现得很有规律,而且都是在差不多的时间间隔内出现。
解决方法
为了减少这种报错信息的出现,我们可以在 php 程序中实现自动重连,即在 MySQL 断开连接前,就自动实现重新连接或者避免再使用 mysqli_ping()
去检测。MySQL 会在什么时候断开连接,可以查看 my.ini
配置中的 wait_timeout
参数。
mysql> show global variables like '%timeout';
+-----------------------------+----------+
| Variable_name | Value |
|-----------------------------+----------|
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| slave_net_timeout | 3600 |
| thread_pool_idle_timeout | 60 |
| wait_timeout | 1800 |
+-----------------------------+----------+
注意最后一项 wait_timeout
的值是 1800 秒,表示 MySQL 会把 30 分钟以上没有任何查询请求的连接给断开。
根据上面的阀值,我们在程序中实现自动重连。
<?php
class DB
{
private static $link;
public static function getLink()
{
if(empty(self::$link) || mysqli_ping(self::$link) === false) {
self::$link = mysqli_connect("host", "username", "password", "dbname");
}
return self::$link;
}
public static function keepConnectionAlive(&$start)
{
$passed = time() - $start;
if($passed > 1800)
{
$start = time();
self::$link = null;
}
}
}
在 PHP 程序中使用(一般会在耗时的 woker 中使用)
$start = time();
while(true)
{
$params = Queues::get();
DB::keepConnectionAlive($start);
$link = DB::getLink();
//...处理业务逻辑
}