应用日志里出现 “Too many connections” 的时候,大多数人第一反应是调大 max_connections。这篇先在本机把 1040 原样复现出来,再看计数器、看连接都在干什么,然后分别试调大 max_connections、缩短 wait_timeout、加连接池三种修法各管什么、各有什么限制。
max_connections = 5 app 连接 1: 成功 app 连接 2: 成功 app 连接 3: 成功 app 连接 4: 成功 app 连接 5: 成功 app 连接 6: OperationalError: (1040, 'Too many connections') root 连接(第 6 个名额,max_connections+1): 成功,此时 processlist 里共 7 个连接 第二个 root 连接: OperationalError: (1040, 'Too many connections')
max_connections=5,5 个 app 连接成功,第 6 个报 (1040, 'Too many connections')。root 有 CONNECTION_ADMIN,能再进一个,第二个 root 同样被拒。这个额外名额只有一个,不要指望它能让很多管理连接同时进来。
Aborted_connects 2 Connection_errors_max_connections 2 Max_used_connections 6 Threads_connected 6 Threads_running 2 interactive_timeout 28800 max_connections 5 thread_handling one-thread-per-connection wait_timeout 28800 说明:Connection_errors_max_connections 是被 1040 拒掉的次数(上面一共被拒了 2 次:第 6 个 app 连接和第二个 root 连接)
Connection_errors_max_connections 是被拒绝的累计次数,Max_used_connections 是历史峰值,Threads_connected 是此刻的连接数。看被拒次数有没有继续涨,就知道是不是还在发生。
user command 个数 最长空闲秒 app Sleep 5 0 event_scheduler Daemon 1 0 root Query 1 0 过 2 秒再看 app 的连接(id, command, 空闲秒): [(16, 'Sleep', 2), (17, 'Sleep', 2), (18, 'Sleep', 2), (19, 'Sleep', 2), (20, 'Sleep', 2)] Sleep 的连接照样占名额:占着连接不放,和有没有在执行 SQL 无关
processlist 里 5 个 app 连接全是 Sleep,过 2 秒空闲时间只是涨到 2 秒。连接数只管“连着多少个”,不管有没有在跑 SQL。所以服务器很闲的时候也可能报 1040。
SET GLOBAL max_connections = 12 之后,已有的 5 个 app 连接还在:5 个 app 连接 6: 成功 app 连接 7: 成功 app 连接 8: 成功 app 连接 9: 成功 SET GLOBAL wait_timeout = 3(此刻已经连着的会话不受影响) 等 5 秒后,改之前开的 5 个老连接还有 5 个活着(会话的 wait_timeout 是连接建立时从全局值拷贝的) 新开的连接 @@session.wait_timeout, @@global.wait_timeout = (3, 3) 新连接空闲 5 秒后再查询: OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))") 这时 processlist 里 app 的连接数: 9
max_connections 用 SET GLOBAL 立即生效。wait_timeout 在连接建立时拷贝成会话变量,改全局值只对之后的连接生效:已经连着的 5 个老连接 5 秒后还全活着,新连接空闲 5 秒就被服务端关掉,客户端再用它时看到 2006。
(root 的 r1 连接还开着,占了 5 个名额里的 1 个,所以 app 同时最多只有 4 个连接) 每线程自己连:成功 4,失败 16;失败的错误:["OperationalError: (1040, 'Too many connections')"];总耗时 0.34 s 连接池(4 个连接,线程排队借还):成功 20,失败 0;总耗时 1.52 s(20 个线程 ÷ 4 个连接 × 0.3 s ≈ 1.5 s) 结论:报 1040 的根源是同一时刻想要的连接数超过上限;池把“同时持有的连接数”封顶,多出来的请求排队而不是报错
每个线程各自 connect,20 个里 16 个直接 1040;换成 4 个连接的池,20 个请求排队用连接,全部成功,耗时 1.52 s。池不会让数据库更快,只是把“失败”换成“等待”。