Jump to Navigation

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

在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:

  1. diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
  2. index ac8d29a..c615aa5 100644
  3. --- a/ext/pdo/pdo_dbh.c
  4. +++ b/ext/pdo/pdo_dbh.c
  5. @@ -425,6 +425,43 @@ options:
  6. }
  7. /* }}} */
  8.  
  9. +static void dbh_free(pdo_dbh_t *dbh TSRMLS_DC);
  10. +/* {{{ proto Boolean PDO::destroy()
  11. + Force destory current PDO Object. Should only be used when got "Mysql has gone away" message. */
  12. +static PHP_METHOD(PDO, destroy)
  13. +{
  14. + pdo_dbh_t *dbh = NULL;
  15. + char *hashkey = NULL;
  16. + zend_rsrc_list_entry *le = NULL;
  17. + int rc = 0;
  18. + zval *object = getThis();
  19. +
  20. + dbh = (pdo_dbh_t *)zend_object_store_get_object(object TSRMLS_CC);
  21. + if (dbh == NULL) {
  22. + RETURN_TRUE;
  23. + }
  24. +
  25. + if (dbh->is_persistent && dbh->persistent_id != NULL) {
  26. + // this is persistent PDO's DSN
  27. + hashkey = dbh->persistent_id;
  28. + rc = zend_hash_find(&EG(persistent_list), hashkey, dbh->persistent_id_len, (void*)&le);
  29. + if (rc == SUCCESS) {
  30. + rc = zend_hash_del(&EG(persistent_list), hashkey, dbh->persistent_id_len);
  31. + if (rc != SUCCESS) {
  32. + }
  33. + assert(le->ptr == dbh);
  34. + php_pdo_pdbh_dtor(le);
  35. + le == NULL;
  36. + }
  37. + }
  38. +
  39. + zval_dtor(object);
  40. + ZVAL_NULL(object);
  41. +
  42. + RETURN_TRUE;
  43. +}
  44. +/* }}} */
  45. +
  46. static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args TSRMLS_DC) /* {{{ */
  47. {
  48. if (ctor_args) {
  49. @@ -1266,6 +1303,7 @@ ZEND_END_ARG_INFO()
  50.  
  51. const zend_function_entry pdo_dbh_functions[] = {
  52. ZEND_MALIAS(PDO, __construct, dbh_constructor, arginfo_pdo___construct, ZEND_ACC_PUBLIC)
  53. + PHP_ME(PDO, destroy, arginfo_pdo__void, ZEND_ACC_PUBLIC)
  54. PHP_ME(PDO, prepare, arginfo_pdo_prepare, ZEND_ACC_PUBLIC)
  55. PHP_ME(PDO, beginTransaction, arginfo_pdo__void, ZEND_ACC_PUBLIC)
  56. PHP_ME(PDO, commit, arginfo_pdo__void, ZEND_ACC_PUBLIC)

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

添加新评论

Plain text

  • 不允许HTML标记。
  • 自动将网址与电子邮件地址转变为链接。
  • 自动断行和分段。
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.


Main menu 2

Story | by Dr. Radut