http://www.ymfeb.cn/articles/65
在用多进程处理对账单时,我发现了一个问题,在子进程关闭后,主进程再次连接数据库时会报MySql server has gone away错误,这是由于在创建子进程时,linux会在父进程的基础上直接拷贝一个子进程,所以这里也会复制出一份数据库连接的socket的文件描述符,父进程和子进程中的socket文件描述符都指向内核中的同一个socket文件(ps:这个地方参考趣说网络协议第十三讲:套接字socket中的内容会更加明白一点),所以当子进程关闭时,也直接把这个socket连接给关闭,导致主进程失去了数据库连接。
所以在这个地方我使用了tp5的断线重连机制,当执行sql时如果检查到数据库返回的是MySql server has gone away,将重新建立一个数据库链接。然后问题出现了,当往数据库进行写操作时,这里陷入了死循环,达到设置的内存上线然后被kill掉。
然后我去看了一下源码,在Connection.php文件的447,抓取到了数据库的异常,然后判断是丢失了连接,所以调用了close方法并再次执行了execute:
} catch (\PDOException $e) {
if ($this->isBreak($e)) {
return $this->close()->execute($sql, $bind);
}
throw new PDOException($e, $this->config, $this->getLastsql());
}
然而在close方法中,并没有释放查询结果:
/**
* 关闭数据库(或者重新连接)
* @access public
* @return $this
*/
public function close()
{
$this->linkID = null;
$this->linkWrite = null;
$this->linkRead = null;
$this->links = [];
return $this;
}
导致在419行,这里$this->PDOStatement->queryString和$sql是相等,从而也不会去释放上一次的PDOStatemen,从而一直使用这个已经失去了连接的PDOStatemen去执行写操作,导致陷入了死循环:
//释放前次的查询结果
if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) {
$this->free();
}
所以解决办法就是在close方法中,将上一次的查询结果释放就行了,看了一下,最新版的tp框架中已经解决了这个问题了:
/**
* 关闭数据库(或者重新连接)
* @access public
* @return $this
*/
public function close()
{
$this->linkID = null;
$this->linkWrite = null;
$this->linkRead = null;
$this->links = [];
// 释放查询
$this->free();
return $this;
}