为MySQL设置查询超时

作者: Laruence 昨天有人在群里问, MySQL是否可以设置读写超时(非连接超时), 如果可以就可以避免一条SQL执行过慢, 导致PHP超时错误. 这个, 其实可以有. 只不过稍微要麻烦点. 首先, 在libmysql中, 是提供了MYSQL_OPT_READ_TIMEOUT设置项的, 并且libmysql中提供了设置相关设置项的API, mysql_options:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int STDCALL
mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg)
{
  DBUG_ENTER("mysql_option");
  DBUG_PRINT("enter",("option: %d",(int) option));
  switch (option) {
  case MYSQL\_OPT\_CONNECT_TIMEOUT:
    mysql->options.connect_timeout= *(uint*) arg;
    break;
  /\*\* 读超时时间 */
  case MYSQL\_OPT\_READ_TIMEOUT:
    mysql->options.read_timeout= *(uint*) arg;
    break;
  case MYSQL\_OPT\_WRITE_TIMEOUT:
    mysql->options.write_timeout= *(uint*) arg;
    break;
  case MYSQL\_OPT\_COMPRESS:
    mysql->options.compress= 1;

   /\* 以下省略 */

但是, 可惜的是, 目前只有mysqli扩展, 把mysql_options完全暴露给了PHP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PHP_FUNCTION(mysqli_options)
{
 /\*\* 有省略 */
     switch (Z\_TYPE\_PP(mysql_value)) {
        /\*\* 没有任何限制, 直接传递给mysql_options */
        case IS_STRING:
            ret = mysql_options(mysql->mysql, mysql_option, Z\_STRVAL\_PP(mysql_value));
            break;
        default:
            convert\_to\_long_ex(mysql_value);
            l_value = Z\_LVAL\_PP(mysql_value);
            ret = mysql_options(mysql->mysql, mysql_option, (char *)&l_value);
            break;
    }

    RETURN_BOOL(!ret);
}

但是因为Mysqli并没有导出这个常量, 所以我们需要通过查看MySQL的代码, 得到MYSQL_OPT_READ_TIMEOUT的实际值, 然后直接调用mysql_options:
1
2
3
4
5
6
7
8
9
10
11
12
enum mysql_option
{
  MYSQL\_OPT\_CONNECT_TIMEOUT, MYSQL\_OPT\_COMPRESS, MYSQL\_OPT\_NAMED_PIPE,
  MYSQL\_INIT\_COMMAND, MYSQL\_READ\_DEFAULT_FILE, MYSQL\_READ\_DEFAULT_GROUP,
  MYSQL\_SET\_CHARSET_DIR, MYSQL\_SET\_CHARSET_NAME, MYSQL\_OPT\_LOCAL_INFILE,
  MYSQL\_OPT\_PROTOCOL, MYSQL\_SHARED\_MEMORY\_BASE\_NAME, MYSQL\_OPT\_READ_TIMEOUT,
  MYSQL\_OPT\_WRITE_TIMEOUT, MYSQL\_OPT\_USE_RESULT,
  MYSQL\_OPT\_USE\_REMOTE\_CONNECTION, MYSQL\_OPT\_USE\_EMBEDDED\_CONNECTION,
  MYSQL\_OPT\_GUESS_CONNECTION, MYSQL\_SET\_CLIENT_IP, MYSQL\_SECURE\_AUTH,
  MYSQL\_REPORT\_DATA_TRUNCATION, MYSQL\_OPT\_RECONNECT,
  MYSQL\_OPT\_SSL\_VERIFY\_SERVER_CERT
};

可以看到, MYSQL_OPT_READ_TIMEOUT为11. 现在, 我们就可以设置查询超时了:
1
2
3
4
<?php
$mysqli = mysqli_init();
$mysqli->options(11 /\*MYSQL\_OPT\_READ_TIMEOUT\*/, 1);
$mysql->real_connect(***);

不过, 因为在libmysql中有重试机制(尝试一次, 重试俩次), 所以, 最终我们设置的超时阈值都会三倍于我们设置的值. 也就是说, 如果我们设置了MYSQL_OPT_READ_TIMEOUT为1, 最终会在3s以后超时结束. 也就是说, 我们目前能设置的最短超时时, 就是3秒… 虽说大了点,, 不过总比没有好, 呵呵 PS: 写了一半的时候, 就发现小黑已经写过一篇了, 所以大家也可以参看这篇PHP访问MySQL查询超时处理