bigywater.tech
常见报错 · 1

MySQL ERROR 1040 Too many connections:SET GLOBAL wait_timeout 回收不了已经连着的闲置连接,先看是谁占着名额

应用日志里出现 “Too many connections” 的时候,大多数人第一反应是调大 max_connections。这篇先在本机把 1040 原样复现出来,再看计数器、看连接都在干什么,然后分别试调大 max_connections、缩短 wait_timeout、加连接池三种修法各管什么、各有什么限制。

6 个
max_connections=5 时 processlist 里同时存在的连接:5 个业务连接加 root 的第 6 个名额;第二个 root 也被拒
5 个
SET GLOBAL wait_timeout = 3 之后 5 秒,改之前开的老连接还活着的个数(一个都没被回收)
16 / 20
20 个线程各自 connect 时失败的个数;同样 20 个线程用 4 个连接的池,失败 0 个、耗时 1.52 s

复现:第 6 个普通连接被拒

实测 · 本机执行输出
  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。

调大 max_connections 与改 wait_timeout

实测 · 本机执行输出
  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。池不会让数据库更快,只是把“失败”换成“等待”。

分步演示

连接数用满
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用一个自己起的 MySQL 26.10.0 实例(--max-connections=5、--performance-schema=OFF、--skip-name-resolve),用 PyMySQL 的 unix socket 连接;root 是 --initialize-insecure 的无密码账号
    • 没有验证:max_connections 的默认值是多少、取值上限;max_user_connections 按账号限连接数的行为;每个连接占多少服务端内存,调到几千会怎样;线程池(thread_handling=pool-of-threads)是否改变 1040 的行为;MySQL 8.0 / 8.4 上的差异;云数据库里参数组怎么改;ProxySQL 之类的中间件对连接数的影响;应用端连接池的各种超时与泄漏问题(这里的池只是一个最小的队列)
    京ICP备2026067502号-1京公网安备11010502063487号