PHP的PDO遇到MySql has gone away的解决方法

发布: 2014-01-27 18:17

在MySQL特定的数据库参数配置的情况下,在一个超时的连接上调用任意的mysql函数,

会导致PHP的PDO扩展报"MySQL has gone away"警告,导致程序无法继续执行。

在使用多种PHP层的解决方案后,依旧无法避免这一问题的出现。

原因在于PDO架构采用了连接对象缓存机制,在使用相同的dsn串连接数据库时,

PDO会从连接池对象中取出有相同dsn串的连接,问题就出在这个地方。

当该连接出现了"MySQL has gone away"之后,PDO并不知道这一情况,

PDO连接对象仍旧在PDO的连接池中。

下次即使在PHP层检测到这一问题,并重试连接,PDO却返回之前报了错的连接。

问题的原因查找到了,接下来就是寻找解决方法。

由于PDO没有提供关闭连接的方法,而是依靠PHP本身的引用计数与垃圾回收机制关闭连接,

在大多数情况下这都没有问题,但这时候这种机制就略显无力了。

经过多次实践修改测试,总结出来以下两种方式,

第一种,修改dsn串后再次连接,这样能得到一个新的PDO连接,并存储到PDO连接池中。
这种情况却有个问题,原有出问题的连接并不会自动释放,
并且还有MySQL的TCP连接存在,会消耗客户端PHP进程与服务器MySQL服务进程的资源。
好处是不需要做大的修改,只需要在PHP加个检测函数即可。

第二种,修改PDO扩展,添加一个PDO方法,把当前对象使用的连接对象从连接池中删除,
并强制销毁当前的PDO对象。
这种好处是修正的比较彻底,但需要修改PDO扩展的C代码。

现在把第二种方法对PDO的补丁代码放上来,以供参考,
php-5.5.8-PDO_destroy.patch:
[code type="diff"]
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index ac8d29a..c615aa5 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -425,6 +425,43 @@ options:
}
/* }}} */

+static void dbh_free(pdo_dbh_t *dbh TSRMLS_DC);
+/* {{{ proto Boolean PDO::destroy()
+ Force destory current PDO Object. Should only be used when got "Mysql has gone away" message. */
+static PHP_METHOD(PDO, destroy)
+{
+ pdo_dbh_t *dbh = NULL;
+ char *hashkey = NULL;
+ zend_rsrc_list_entry *le = NULL;
+ int rc = 0;
+ zval *object = getThis();
+
+ dbh = (pdo_dbh_t *)zend_object_store_get_object(object TSRMLS_CC);
+ if (dbh == NULL) {
+ RETURN_TRUE;
+ }
+
+ if (dbh->is_persistent && dbh->persistent_id != NULL) {
+ // this is persistent PDO's DSN
+ hashkey = dbh->persistent_id;
+ rc = zend_hash_find(&EG(persistent_list), hashkey, dbh->persistent_id_len, (void*)&le);
+ if (rc == SUCCESS) {
+ rc = zend_hash_del(&EG(persistent_list), hashkey, dbh->persistent_id_len);
+ if (rc != SUCCESS) {
+ }
+ assert(le->ptr == dbh);
+ php_pdo_pdbh_dtor(le);
+ le == NULL;
+ }
+ }
+
+ zval_dtor(object);
+ ZVAL_NULL(object);
+
+ RETURN_TRUE;
+}
+/* }}} */
+
static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args TSRMLS_DC) /* {{{ */
{
if (ctor_args) {
@@ -1266,6 +1303,7 @@ ZEND_END_ARG_INFO()

const zend_function_entry pdo_dbh_functions[] = {
ZEND_MALIAS(PDO, __construct, dbh_constructor, arginfo_pdo___construct, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, destroy, arginfo_pdo__void, ZEND_ACC_PUBLIC)
PHP_ME(PDO, prepare, arginfo_pdo_prepare, ZEND_ACC_PUBLIC)
PHP_ME(PDO, beginTransaction, arginfo_pdo__void, ZEND_ACC_PUBLIC)
PHP_ME(PDO, commit, arginfo_pdo__void, ZEND_ACC_PUBLIC)

[/code]

这样使用PDO的PHP框架就能比较完善的使用MySQL了。
这段补丁代码针对php-5.5.8版本,请在打补丁时使用相应的php源代码包。
如需要使用这个补丁,请先仔细测试。
目前刚写完代码,做了简单的测试,还没有在正式项目中应用。



原文: http://qtchina.tk/?q=node/785

Powered by zexport