当超过 mysql 的 wait_timeout 时,我的 PHP CLI 脚本失去了连接。我无法更改 wait_timeout,那么当我使用 PDOStatement 执行查询时,如何构建一个重新连接的 try/catch 语句?
3 回答
在发生错误后重新连接到数据库实际上是一个比最初看起来要复杂得多的问题。
我的第一个想法是为 PDO 编写一个简单的包装类,它将方法代理到内部 PDO 对象上,并且可以自己处理连接错误:
class BetterPDO extends PDO
{
private $realPDO = NULL;
private $dsn = "";
private $username = "";
private $password = "";
private $options = [];
public function __construct ($dsn, $username = "", $password = "", $options = [])
{
$this -> dsn = $dsn;
$this -> username = $username;
$this -> password = $password;
$this -> options = $options;
}
private function getRealPDO ()
{
if (is_null ($this -> realPDO))
{
$this -> realPDO = new PDO ($this -> dsn, $this -> username, $this -> password, $this -> options);
}
return $this -> realPDO;
}
// We're only implementing exec for brevity but you have to do this for all public methods of PDO
public function exec ($sql)
{
$retries = 0;
while (true)
{
try
{
return $this -> getRealPDO () -> exec ($sql);
}
catch (PDOException $ex)
{
$this -> realPDO = NULL;
if (++$retries > 5)
{
// We've passed our retry limit
throw $ex;
}
}
}
}
}
由于此类扩展了 PDO,因此可以在任何可以使用通用 PDO 类的地方使用它。
正如你所看到的,这种方法会在 exec() 方法放弃之前给你一些重试,允许在瞬态错误之后重新连接(这只是为了演示,缺少一些真正实现需要的功能,比如重试之间的退避,足够的错误记录等)。这种方法还需要您检查抛出的 PDO 异常的细节,理由是您不希望诸如 MySQL 语法错误之类的事情导致连接被重置并尝试重试。您只希望它发生在诸如“服务器已消失”之类的事情上。
正如您还可以看到的,实现所有代理 PDO 方法将成为一件苦差事,尽管您只需要做一次,这可能值得投入精力去做。
但是还有一个更大的问题,对于任何与数据库对话的代码来说,这几乎是一个普遍的问题,而不仅仅是 PDO。如果在事务处理过程中连接丢失怎么办?在这种情况下,您不希望您的脚本重新连接并从中断处继续,因为您在最后一次提交之前所做的所有工作都将丢失,并且恢复可能没有逻辑意义重新连接后,您必须重新开始。因此,您可能只希望整个脚本重新开始,而尝试重新连接没有任何意义。这可能是 mySQLI 支持重新连接但 PDO 不支持的原因。
如果您的脚本只进行读取或非事务性写入,那么上述方法仍然有价值,但是一旦您将事务投入混合中,您实际上最好不要尝试重新连接。
最好的方法是将 PDO 实例创建包装到一个存储实例和创建时间的单例(即 MyPDOFactory)中,这样,您可以重用它或在达到 TTL 后重新创建它(2 或 3 秒是对于大多数应用程序来说绰绰有余)。你只需要调用 MyPDOFactory::get() 来获得一个有效的 PDO,你可以用它来准备 PDOStatement,只要确保你尽快执行它。
我认为这可以帮助你。
/* Your Database Name */
$dbname = 'mydatabase';
/* Your Database User Name and Passowrd */
$username = 'root';
$password = 'password';
try {
/* Establish the database connection */
$conn = new PDO("mysql:host=localhost;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/* your code goes here*/
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
//mysql_close($conn);
$conn=null;