MySQL 案例:"最大连接数"的隐形限制

2020-11-25 16:53:36 浏览数 (1)

问题描述

最近遇到一个比较奇怪的问题,用户反馈云服务器的自建 MySQL 连接数没达到的 max_connections 限制,但是程序侧已经开始报错,无法创建新的连接了。程序端报错信息如下:

代码语言:txt复制
perationalError(1135, "Can't create a new thread (errno 11); if you are not out of available memory, 
you can consult the manual for a possible OS-dependent bug")

MySQL 侧的错误日志显示:

代码语言:txt复制
Can't create thread to handle new connection(errno= 11)

原因分析

如果是触发了最大连接数的限制,错误信息应该是Too many connections,因此这次遇到的问题应该不是参数方面的问题。

值得注意的是,报错信息中显示了 errorno,这个一般是系统层面抛出的异常 Code,因此看一下这个 11 代表的意义:

OS error code 11: Resource temporarily unavailable

那么问题就比较明显了,这个问题是部分资源不可用引起的,说明是收到了外部(即 Linux)的影响才报错的。那么按照用户的环境,搭建了一个沙盒环境,写了一个简单的 python 脚本(参考附录),发现创建的连接数达到一定的数量之后确认会报错,且抛出的异常信息和用户反馈的信息一致:

代码语言:txt复制
root@VM-64-5-debian:~# python3 py_conn.py
hello world
OperationalError(1135, "Can't create a new thread (errno 11); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug")
Current MAX_CONNECTION =  32455
5.7.32-log
5.7.32-log
......

在多次尝试过程中,发现一个现象:如果是从 thread_cache 中直接复用的线程是不会触发这个问题的,只有新建连接的时候才会触发。那么追踪了一下 MySQL 创建连接的流程,发现在./sql/conn_handler/connection_handler_per_thread.cc中,以 mysql_thread_create 方法的返回值为条件,会输出这个错误信息。之后以这个方法为入口,最终定位到了原因:调用系统库创建线程的时候出错了。

之后继续查找系统库相关的资料,发现创建线程的数量会受到 Linux 的参数:vm.max_map_count的限制。检查了一下这个参数的作用:

“This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries.

While most applications need less than a thousand maps, certain programs, particularly malloc debuggers, may consume lots of them, e.g., up to one or two maps per allocation.

The default value is 65536.”

简单来说,进程创建线程的时候会创建一些虚拟内存区域,而这个参数限制了这个区域的数量,因此 MySQL 的可创建的连接数也会受到这个参数的限制。

实际测试一下效果:

代码语言:txt复制
Session 1:
root@debian:~# sysctl -w vm.max_map_count=256
vm.max_map_count = 256
root@debian:~# service mysql restart
root@debian:~# mysql57
......
Server version: 5.7.32-log MySQL Community Server (GPL)
......
mysql> select count(*) from performance_schema.threads;
 ---------- 
| count(*) |
 ---------- 
|       59 |
 ---------- 
1 row in set (0.00 sec)

mysql> show variables like '%max_conn%';
 -------------------- ------- 
| Variable_name      | Value |
 -------------------- ------- 
| max_connect_errors | 100   |
| max_connections    | 151   |
 -------------------- ------- 
2 rows in set (0.01 sec)

Session 2:
root@debian:~# python3 py_conn.py
hello world
OperationalError(1135, "Can't create a new thread (errno 11); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug")
Current MAX_CONNECTION =  26
5.7.32-log
5.7.32-log
5.7.32-log
5.7.32-log

可以发现调低了这个系统参数之后,尝试创建连接的时候就会报错,而且可用的最大连接数非常低。

总结一下

这个案例属于比较典型的“受牵连”,即 MySQL 因为外部的限制导致问题的发生,DBA 们在排查问题的时候不仅需要考虑到 MySQL 的问题,也要留意是否是外部原因影响了 MySQL 的行为。

附录

Python3.7,py_conn.py。

代码语言:txt复制
import MySQLdb
import time

def dbconn():
    try:
        conn = MySQLdb.connect(host="127.0.0.1",user="root",passwd="root",charset="utf8mb4")
        return conn
    except Exception as e:
        print(repr(e))
        return None

def main():
    print("hello world")
    conn_list = []
    num = 65535
    couter = 0
    for counter in range(0,num):
        conn = dbconn()
        if conn is None : break
        conn_list.append(conn) 
    i = 0
    print("Current MAX_CONNECTION = ",counter 1)
    while(True):
        tmp_conn = conn_list[i]
        cursor = tmp_conn.cursor()
        n = cursor.execute("select version()")
        for row in cursor.fetchall():
            for r in row:
                print(r)
        if (i == counter - 1): i = 0
        i = i   1
        time.sleep (1)

if __name__ == "__main__":
    main()

0 人点赞