问题描述
最近遇到一个比较奇怪的问题,用户反馈云服务器的自建 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()