为 PDO 增加读写超时

2015-12-10 22:26:22   最后更新: 2015-12-11 17:47:03   访问数量:4613




pdo_mysql 是一个十分成熟易用的 PHP mysql 操作扩展,它支持面向对象、prepare 等操作实现对 mysql 操作的封装,实现了高度的定制化和安全性

PHP5 中,pdo_mysql 是作为基本组件随 PHP 一起安装的,下面的文章系统的介绍了 pdo_mysql 的基本用法:

ERROR:加载失败

 

然而,pdo_mysql 有一个限制因素,那就是他只能设置连接超时,而并没有提供读、写超时的设置,这显然具有很大的安全隐患

昨天,公司线上发生了一个事故,由于 DB 机器问题,造成读写时间过长,而造成了队列的大量积压,当然,发生这样的问题是多方面原因和责任造成的,但是作为调用方,在调用时设置读写超时也是十分必要的

那么,pdo_mysql 没有提供读写超时的机制,我们怎么添加呢?通过修改 pdo_mysql 的源码即可实现这一操作,事实上,这一过程并不复杂

 

PHP 扩展组件全部存放在 ext 目录中,我们要阅读和修改的 pdo_mysql 组件的源码也在其中,进入 ext/pdo_mysql 目录,我们可以看到几个源码文件和测试脚本文件

使用 C 语言调用过 mysql 的同学一定知道,C 语言是通过 mysql_options 函数设置 mysql 的各种属性的,包括连接超时、读写超时,所以我们可以 grep 一下这个函数,找到 mysql_driver.c

找到了下面的代码:

if (mysql_options(H->server, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout)) { pdo_mysql_error(dbh); goto cleanup; }

 

 

没错,H->server 就是指向 MYSQL 结构体对象的指针,而 connect_timeout 就是 long 类型的超时时间

 

接着,我们再看一下 connect_timeout 是如何获取的:

long connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC);

 

 

我们看到,是通过 pdo_attr_lval 这个函数获取的:

static inline long pdo_attr_lval(zval *options, enum pdo_attribute_type option_name, long defval TSRMLS_DC) { zval **v; if (options && SUCCESS == zend_hash_index_find(Z_ARRVAL_P(options), option_name, (void**)&v)) { convert_to_long_ex(v); return Z_LVAL_PP(v); } return defval; }

 

这个函数返回了枚举类型 pdo_attribute_type 变量 option_name 在哈希表 options 中对应的值,如果没有设置,那么就取默认的 30s

 

整个流程我们已经很清楚了,只需要依葫芦画瓢,就可以完成读写超时的添加了

 

函数定义:

int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg);

 

具体用法可以参看:MySQL 5.1 Reference Manual 21.8.7.49 mysql_options()

在 mysql_init() 调用后,调用 mysql_connect() 或 mysql_real_connect() 前调用 mysql_options 可以实现很多设置

他的第三个参数会根据第二个参数的取值而指向不同的结构类型,这个日后抽时间总结一下,因为参数取值太多,这里先不一一介绍了

总之,连接超时的 option 取 MYSQL_OPT_CONNECT_TIMEOUT,读写超时的 option 分别取 MYSQL_OPT_READ_TIMEOUT 和 MYSQL_OPT_WRITE_TIMEOUT

第三个参数都是一个 long 类型地址,存储超时的秒数

 

定义枚举类型

首先,我们先要创建枚举类型,也就是未来在 PHP 代码中,setAttribute 传入的 KEY

 

原生的这个 key 值都定义在 Zend/ext/pdo/php_pdo_driver.h 中:

enum pdo_attribute_type { PDO_ATTR_AUTOCOMMIT, /* use to turn on or off auto-commit mode */ PDO_ATTR_PREFETCH, /* configure the prefetch size for drivers that support it. Size is in KB */ PDO_ATTR_TIMEOUT, /* connection timeout in seconds */ PDO_ATTR_ERRMODE, /* control how errors are handled */ PDO_ATTR_SERVER_VERSION, /* database server version */ PDO_ATTR_CLIENT_VERSION, /* client library version */ PDO_ATTR_SERVER_INFO, /* server information */ PDO_ATTR_CONNECTION_STATUS, /* connection status */ PDO_ATTR_CASE, /* control case folding for portability */ PDO_ATTR_CURSOR_NAME, /* name a cursor for use in "WHERE CURRENT OF <name>" */ PDO_ATTR_CURSOR, /* cursor type */ PDO_ATTR_ORACLE_NULLS, /* convert empty strings to NULL */ PDO_ATTR_PERSISTENT, /* pconnect style connection */ PDO_ATTR_STATEMENT_CLASS, /* array(classname, array(ctor_args)) to specify the class of the constructed statement */ PDO_ATTR_FETCH_TABLE_NAMES, /* include table names in the column names, where available */ PDO_ATTR_FETCH_CATALOG_NAMES, /* include the catalog/db name names in the column names, where available */ PDO_ATTR_DRIVER_NAME, /* name of the driver (as used in the constructor) */ PDO_ATTR_STRINGIFY_FETCHES, /* converts integer/float types to strings during fetch */ PDO_ATTR_MAX_COLUMN_LEN, /* make database calculate maximum length of data found in a column */ PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */ PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */ /* this defines the start of the range for driver specific options. * Drivers should define their own attribute constants beginning with this * value. */ PDO_ATTR_DRIVER_SPECIFIC = 1000 };

 

 

注意最后的注释:

/* this defines the start of the range for driver specific options. * Drivers should define their own attribute constants beginning with this * value. */

 

 

我们不能去修改这个文件,而是要以 PDO_ATTR_DRIVER_SPECIFIC 为起始构造自己的枚举类型,0 到 PDO_ATTR_DRIVER_SPECIFIC 之间的值是 PHP zend 内核的保留区段

我们回到 ext/pdo_mysql 目录中,查找一下 PDO_ATTR_DRIVER_SPECIFIC,可以找到,在 php_pdo_mysql_int.h 中找到:

enum { PDO_MYSQL_ATTR_USE_BUFFERED_QUERY = PDO_ATTR_DRIVER_SPECIFIC, PDO_MYSQL_ATTR_LOCAL_INFILE, PDO_MYSQL_ATTR_INIT_COMMAND, #ifndef PDO_USE_MYSQLND PDO_MYSQL_ATTR_READ_DEFAULT_FILE, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, #endif PDO_MYSQL_ATTR_COMPRESS, PDO_MYSQL_ATTR_DIRECT_QUERY, PDO_MYSQL_ATTR_FOUND_ROWS, PDO_MYSQL_ATTR_IGNORE_SPACE, PDO_MYSQL_ATTR_SSL_KEY, PDO_MYSQL_ATTR_SSL_CERT, PDO_MYSQL_ATTR_SSL_CA, PDO_MYSQL_ATTR_SSL_CAPATH, PDO_MYSQL_ATTR_SSL_CIPHER, #if MYSQL_VERSION_ID > 50605 || defined(PDO_USE_MYSQLND) PDO_MYSQL_ATTR_SERVER_PUBLIC_KEY #endif };

 

 

这里就是 PDO 自己设置的 PDO_ATTR_DRIVER_SPECIFIC 之后的枚举数据我们只需要在这里定义我们需要的枚举类型即可:

enum { PDO_MYSQL_ATTR_USE_BUFFERED_QUERY = PDO_ATTR_DRIVER_SPECIFIC, PDO_MYSQL_ATTR_LOCAL_INFILE, PDO_MYSQL_ATTR_INIT_COMMAND, #ifndef PDO_USE_MYSQLND PDO_MYSQL_ATTR_READ_DEFAULT_FILE, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, #endif PDO_MYSQL_ATTR_COMPRESS, PDO_MYSQL_ATTR_DIRECT_QUERY, PDO_MYSQL_ATTR_FOUND_ROWS, PDO_MYSQL_ATTR_IGNORE_SPACE, PDO_MYSQL_ATTR_SSL_KEY, PDO_MYSQL_ATTR_SSL_CERT, PDO_MYSQL_ATTR_SSL_CA, PDO_MYSQL_ATTR_SSL_CAPATH, PDO_MYSQL_ATTR_SSL_CIPHER, PDO_MYSQL_RW_ENV_ALL, PDO_MYSQL_RW_ENV_CLI, PDO_MYSQL_RW_ENV_WEB, PDO_MYSQL_RW_ENV_NONE, PDO_MYSQL_ATTR_RW_TIMEOUT_ENV, PDO_MYSQL_ATTR_WRITE_TIMEOUT, PDO_MYSQL_ATTR_READ_TIMEOUT, #if MYSQL_VERSION_ID > 50605 || defined(PDO_USE_MYSQLND) PDO_MYSQL_ATTR_SERVER_PUBLIC_KEY #endif };

 

 

这里我们添加了 7 个枚举类型:

  1. PDO_MYSQL_ATTR_READ_TIMEOUT -- 读超时
  2. PDO_MYSQL_ATTR_WRITE_TIMEOUT -- 写超时
  3. PDO_MYSQL_ATTR_RW_TIMEOUT_ENV -- 环境

 

PDO_MYSQL_ATTR_RW_TIMEOUT_ENV 这个 key 我们用来设定我们的设置在哪些环境下生效:

  1. PDO_MYSQL_RW_ENV_ALL -- 全部环境中都生效
  2. PDO_MYSQL_RW_ENV_CLI -- 只在 cli 调用时生效
  3. PDO_MYSQL_RW_ENV_WEB -- 只在 web 调用时生效
  4. PDO_MYSQL_RW_ENV_NONE -- 不生效

 

注册枚举类型

定义枚举类型以后,我们需要向 PHP 内核注册我们用到的 key 的具体字符串值及对应的枚举类型值

在 pdo_mysql.c 中的模块初始化函数中注册我们的枚举类型及 key 值:

/* true global environment */ /* {{{ PHP_MINIT_FUNCTION */ static PHP_MINIT_FUNCTION(pdo_mysql) { REGISTER_INI_ENTRIES(); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_USE_BUFFERED_QUERY", (long)PDO_MYSQL_ATTR_USE_BUFFERED_QUERY); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_LOCAL_INFILE", (long)PDO_MYSQL_ATTR_LOCAL_INFILE); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_INIT_COMMAND", (long)PDO_MYSQL_ATTR_INIT_COMMAND); #ifndef PDO_USE_MYSQLND REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_MAX_BUFFER_SIZE", (long)PDO_MYSQL_ATTR_MAX_BUFFER_SIZE); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_READ_DEFAULT_FILE", (long)PDO_MYSQL_ATTR_READ_DEFAULT_FILE); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_READ_DEFAULT_GROUP", (long)PDO_MYSQL_ATTR_READ_DEFAULT_GROUP); #endif REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_COMPRESS", (long)PDO_MYSQL_ATTR_COMPRESS); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_DIRECT_QUERY", (long)PDO_MYSQL_ATTR_DIRECT_QUERY); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_FOUND_ROWS", (long)PDO_MYSQL_ATTR_FOUND_ROWS); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_IGNORE_SPACE", (long)PDO_MYSQL_ATTR_IGNORE_SPACE); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SSL_KEY", (long)PDO_MYSQL_ATTR_SSL_KEY); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SSL_CERT", (long)PDO_MYSQL_ATTR_SSL_CERT); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SSL_CA", (long)PDO_MYSQL_ATTR_SSL_CA); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SSL_CAPATH", (long)PDO_MYSQL_ATTR_SSL_CAPATH); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SSL_CIPHER", (long)PDO_MYSQL_ATTR_SSL_CIPHER); // 注册枚举类型及对应的 KEY 值 REGISTER_PDO_CLASS_CONST_LONG("MYSQL_RW_ENV_ALL", (long)PDO_MYSQL_RW_ENV_ALL); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_RW_ENV_CLI", (long)PDO_MYSQL_RW_ENV_CLI); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_RW_ENV_WEB", (long)PDO_MYSQL_RW_ENV_WEB); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_RW_ENV_NONE", (long)PDO_MYSQL_RW_ENV_NONE); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_RW_TIMEOUT_ENV", (long)PDO_MYSQL_ATTR_RW_TIMEOUT_ENV); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_WRITE_TIMEOUT", (long)PDO_MYSQL_ATTR_WRITE_TIMEOUT); REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_READ_TIMEOUT", (long)PDO_MYSQL_ATTR_READ_TIMEOUT); #if MYSQL_VERSION_ID > 50605 || defined(PDO_USE_MYSQLND) REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SERVER_PUBLIC_KEY", (long)PDO_MYSQL_ATTR_SERVER_PUBLIC_KEY); #endif #ifdef PDO_USE_MYSQLND mysqlnd_reverse_api_register_api(&pdo_mysql_reverse_api TSRMLS_CC); #endif return php_pdo_register_driver(&pdo_mysql_driver); }

 

 

获取传入的 value

接下来,我们需要获取调用 php 时传入的具体设置的超时时间值及限制的调用方式值,只有获取后才能实现设置

与连接超时的设置相同,我们使用 pdo 实现的 pdo_attr_lval 函数:

long write_timeout = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_WRITE_TIMEOUT, 60 TSRMLS_CC); long read_timeout = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_READ_TIMEOUT, 60 TSRMLS_CC); long read_write_timeout_env = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_RW_TIMEOUT_ENV, PDO_MYSQL_RW_ENV_CLI TSRMLS_CC);

 

 

判断调用方式

下面我们通过调用方式判断是否启用相应的设置

php 提供了 sapi_module.name 变量作为 cli 调用或 web 调用的标识:

int enable_read_write_timeout = 0; //all env if (read_write_timeout_env == PDO_MYSQL_RW_ENV_ALL) { enable_read_write_timeout = 1; } // web else if ( read_write_timeout_env == PDO_MYSQL_RW_ENV_WEB && strcmp(sapi_module.name, "cli") != 0 ) { enable_read_write_timeout = 1; } // cli else if ( read_write_timeout_env == PDO_MYSQL_RW_ENV_CLI && strcmp(sapi_module.name, "cli") == 0 ) { enable_read_write_timeout = 1; } else if ( read_write_timeout_env == PDO_MYSQL_RW_ENV_NONE ) { enable_read_write_timeout = 0; }

 

 

设置超时时间

万事俱备,只差最后一步,设置超时时间,正如我们前面所说,这里我们使用 mysql_options 设置读写超时:

if (enable_read_write_timeout == 1) { if (mysql_options(H->server, MYSQL_OPT_WRITE_TIMEOUT, (const char *)&write_timeout)) { pdo_mysql_error(dbh); goto cleanup; } if (mysql_options(H->server, MYSQL_OPT_READ_TIMEOUT, (const char *)&read_timeout)) { pdo_mysql_error(dbh); goto cleanup; } }

 

 

完成

重新编译 PHP 即可

 

也许细心的你发现,pdo_mysql 源码中总是有一个判断:

#ifndef PDO_USE_MYSQLND

 

这里的 Mysqlnd 指的是:Mysql Native Driver,是 php 对 mysql 调用的本地封装

我们上面的修改都是基于 libmysql 的,而对于使用 mysqlnd 则需要使用另外一种方法来设置读写超时

 

MYSQLND 的创建 -- mysqlnd_init

#if defined(PDO_USE_MYSQLND) # define pdo_mysql_init(persistent) mysqlnd_init(MYSQLND_CLIENT_NO_FLAG, persistent) #else # define pdo_mysql_init(persistent) mysql_init(NULL) #endif

 

 

我们进入 mysqlnd_init 这个函数,它在 Zend/ext/mysqlnd/mysqlnd.h 中声明如下:

/* Connect */ #define mysqlnd_init(client_flags, persistent) _mysqlnd_init((client_flags), (persistent) TSRMLS_CC) PHPAPI MYSQLND * _mysqlnd_init(unsigned int client_flags, zend_bool persistent TSRMLS_DC);

 

 

接着,我们进入 Zend/ext/mysqlnd/mysqlnd.c 查看他的定义:

/* {{{ _mysqlnd_init */ PHPAPI MYSQLND * _mysqlnd_init(unsigned int flags, zend_bool persistent TSRMLS_DC) { MYSQLND * ret; DBG_ENTER("mysqlnd_init"); ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_connection(persistent TSRMLS_CC); if (ret && ret->data) { ret->data->m->negotiate_client_api_capabilities(ret->data, flags TSRMLS_CC); } DBG_RETURN(ret); } /* }}} */

 

具体的调用和返回这里不多说,只需要看到他调用的是 get_connection 这个函数,这个函数是 st_mysqlnd_object_factory_methods 结构中的一个函数指针,也就是一个回调函数

struct st_mysqlnd_object_factory_methods { func_mysqlnd_object_factory__get_connection get_connection; func_mysqlnd_object_factory__clone_connection_object clone_connection_object; func_mysqlnd_object_factory__get_prepared_statement get_prepared_statement; func_mysqlnd_object_factory__get_io_channel get_io_channel; func_mysqlnd_object_factory__get_protocol_decoder get_protocol_decoder; };

 

 

我们再进入 func_mysqlnd_object_factory__get_connection 的声明:

typedef MYSQLND * (*func_mysqlnd_object_factory__get_connection)(zend_bool persistent TSRMLS_DC);

 

 

MYSQLND 实例结构

_mysqlnd_init 函数返回了一个 MYSQLND 类型,他又是什么?

typedef struct st_mysqlnd_connection MYSQLND; struct st_mysqlnd_connection { MYSQLND_CONN_DATA * data; zend_bool persistent; struct st_mysqlnd_conn_methods * m; };

 

 

这个结构中有三个字段,可以看得出来,最重要的是 MYSQLND_CONN_DATA 类型的 data 字段

我们进入 MYSQLND_CONN_DATA 这个结构:

typedef struct st_mysqlnd_connection_data MYSQLND_CONN_DATA; struct st_mysqlnd_connection_data { /* Operation related */ MYSQLND_NET * net; MYSQLND_PROTOCOL * protocol; /* Information related */ char *host; unsigned int host_len; char *unix_socket; unsigned int unix_socket_len; char *user; unsigned int user_len; char *passwd; unsigned int passwd_len; char *scheme; unsigned int scheme_len; uint64_t thread_id; char *server_version; char *host_info; zend_uchar *auth_plugin_data; size_t auth_plugin_data_len; const MYSQLND_CHARSET *charset; const MYSQLND_CHARSET *greet_charset; char *connect_or_select_db; unsigned int connect_or_select_db_len; MYSQLND_INFILE infile; unsigned int protocol_version; unsigned long max_packet_size; unsigned int port; unsigned long client_flag; unsigned long server_capabilities; /* For UPSERT queries */ MYSQLND_UPSERT_STATUS * upsert_status; MYSQLND_UPSERT_STATUS upsert_status_impl; char *last_message; unsigned int last_message_len; /* If error packet, we use these */ MYSQLND_ERROR_INFO * error_info; MYSQLND_ERROR_INFO error_info_impl; /* To prevent queries during unbuffered fetches. Also to mark the connection as destroyed for garbage collection. */ enum mysqlnd_connection_state state; enum_mysqlnd_query_type last_query_type; /* Temporary storage between query and (use|store)_result() call */ MYSQLND_RES *current_result; /* How many result sets reference this connection. It won't be freed until this number reaches 0. The last one, please close the door! :-) The result set objects can determine by inspecting 'quit_sent' whether the connection is still valid. */ unsigned int refcount; /* Temporal storage for mysql_query */ unsigned int field_count; /* options */ MYSQLND_OPTIONS * options; MYSQLND_OPTIONS options_impl; /* stats */ MYSQLND_STATS * stats; unsigned int client_api_capabilities; struct st_mysqlnd_conn_data_methods * m; /* persistent connection */ zend_bool persistent; };

 

 

我们需要修改网络连接设置,进入 MYSQLND_NET 类型看下:

typedef struct st_mysqlnd_net MYSQLND_NET; struct st_mysqlnd_net { struct st_mysqlnd_net_data * data; /* sequence for simple checking of correct packets */ zend_uchar packet_no; zend_uchar compressed_envelope_packet_no; #ifdef MYSQLND_COMPRESSION_ENABLED MYSQLND_READ_BUFFER * uncompressed_data; #else void * unused_pad1; #endif /* cmd buffer */ MYSQLND_CMD_BUFFER cmd_buffer; zend_bool persistent; };

 

 

我们再进入 st_mysqlnd_net_data:

struct st_mysqlnd_net_data { php_stream *stream; zend_bool compressed; zend_bool ssl; #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND zend_uchar last_command; #else zend_uchar unused_pad1; #endif MYSQLND_NET_OPTIONS options; unsigned int refcount; zend_bool persistent; struct st_mysqlnd_net_methods m; };

 

 

这时,你看到了什么?options !!!

 

mysqlnd 配置结构 -- MYSQLND_NET_OPTIONS

我们进入这个配置结构 MYSQLND_NET_OPTIONS:

typedef struct st_mysqlnd_net_options { /* timeouts */ unsigned int timeout_connect; unsigned int timeout_read; unsigned int timeout_write; size_t net_read_buffer_size; /* SSL information */ char *ssl_key; char *ssl_cert; char *ssl_ca; char *ssl_capath; char *ssl_cipher; char *ssl_passphrase; zend_bool ssl_verify_peer; uint64_t flags; char * sha256_server_public_key; char * unused1; char * unused2; char * unused3; char * unused4; } MYSQLND_NET_OPTIONS;

 

 

这里有 timeout_connect、timeout_read、timeout_write 三个字段,也就说,我们只需要设置他就可以实现读写超时的配置吗?

 

设置读写超时

将我们之前的设置:

if (enable_read_write_timeout == 1) { if (mysql_options(H->server, MYSQL_OPT_WRITE_TIMEOUT, (const char *)&write_timeout)) { pdo_mysql_error(dbh); goto cleanup; } if (mysql_options(H->server, MYSQL_OPT_READ_TIMEOUT, (const char *)&read_timeout)) { pdo_mysql_error(dbh); goto cleanup; } }

 

 

改为:

if (enable_read_write_timeout == 1) { #ifndef PDO_USE_MYSQLND if (mysql_options(H->server, MYSQL_OPT_WRITE_TIMEOUT, (const char *)&write_timeout)) { pdo_mysql_error(dbh); goto cleanup; } if (mysql_options(H->server, MYSQL_OPT_READ_TIMEOUT, (const char *)&read_timeout)) { pdo_mysql_error(dbh); goto cleanup; } #else H->server->data->net->data->options.timeout_read = (uint) read_timeout; H->server->data->net->data->options.timeout_write = (uint) write_timeout; #endif }

 

 

大功告成!

 

下面就到了激动人心的测试阶段了

<?php $mysql_config = 'mysql:host=localhost;dbname=test'; $pdo = new PDO($mysql_config, 'test', 'password', array( PDO::MYSQL_ATTR_RW_TIMEOUT_ENV => PDO::MYSQL_RW_ENV_CLI, PDO::MYSQL_ATTR_READ_TIMEOUT => 3, PDO::MYSQL_ATTR_WRITE_TIMEOUT => 3, ) ); $sql = 'select sleep(1)'; $stmt = $pdo->prepare($sql); $stmt->execute(); $ret = $stmt->fetchAll(); var_dump($ret); ?>

 

 

调用结果返回正常

 

 

然后,我们把代码中的 sleep(1) 改成 sleep(5),即可看到异常返回

 

 

经查看 php 源码,上文中的 php 版本是 5.6.3,如果是在 5.4.* 版本中,mysqlnd 结构有所不同,应该使用:

<?php H->server->data->net->options.timeout_read = (uint) read_timeout; H->server->data->net->options.timeout_write = (uint) write_timeout; ?>

 

 

其他版本我没有看,不过分析的过程是一样的,根据上文中的方法按图索骥即可找到设置的正确位置和结构

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 






技术帖      php      mysql      技术分享      源码      opensource      sourcecode      pdo      source      超时      timeout     


1#dplord: (回复)2015-12-11 10:41:10

兄台写的好好啊,学习了。哈哈,我是meituan跟你一个组的新童鞋dplord

2#博主: (回复)2015-12-11 13:35:43

回复:1#嘿嘿,谢谢支持,你的博客也不错,今后合作愉快 ^_^

京ICP备15018585号