> ## Content Index
> Fetch the complete content index at: https://blog.20061124.xyz/llms.txt
> Use this file to discover other available public pages before exploring further.

# MySQL 连接池的坑：min 参数让连接成对断开
- URL: https://blog.20061124.xyz/mysql-pool-min/
- Published: 2026-09-24T02:00:00.000Z
- Updated: 2026-10-01T06:56:59.000Z
- Description: 日志里每隔一两天就成对冒出两条 Connection Error。把「为什么是两条」和「为什么是一两天」拆开看，问题就清楚了。
- Author: Ray Jack
- Tags: 数据库

前段时间翻服务器日志，发现 Ghost 每隔一两天就会报两条一模一样的错误：

```
Connection Error: Error: The client was disconnected by the server
because of inactivity. See wait_timeout and interactive_timeout
for configuring this behavior.

```

两个奇怪的地方：一是它从来不成单出现，永远是成对的两条；二是它每隔一两天才来一次，中间毫无规律。

把这两个"不对劲"拆开看，问题其实很清楚了。

## 一、为什么是成对出现

数字「2」本身就是答案的一半。Ghost 用的是 knex，底层的连接池是 tarn。tarn 的默认配置里有一个参数：

```
min: 2

```

`min` 的意思是「池子里至少保留几条连接」。关键在于 tarn 的回收规则——**低于 `min` 的连接永远不会被回收**。也就是说，只要这两条连接建立过一次，它们就会被永久占着，哪怕博客一整天没人访问。

所以日志里永远是成对的：池子里雷打不动地蹲着两条空闲连接。

## 二、为什么每隔一两天才报错

MySQL 那边有个参数叫 `wait_timeout`，默认值 28800 秒，也就是 8 小时。它的意思是：一条连接如果 8 小时没有任何活动，MySQL 就主动把它掐掉。

把这个和上面接起来，现象就解释通了：

- Ghost 池子里有两条连接，闲着不动
- 博客访问量低，经常超过 8 小时没人来
- MySQL 到点把这俩掐了
- tarn 下次用到它们时才发现是断的，于是报错

一次掐两条，所以报错也是两条。至于「一两天一次」，取决于这段时间里到底隔了多久没人访问——超过 8 小时才会触发。

## 三、光推测不算数，得验证

上面都是推测。要确认，得从三个角度下手：

1. **看配置有没有传下去**。Ghost 的 `core/server/data/db/connection.js` 里，配置对象是直接丢给 knex 的，所以 `config.production.json` 里的 `pool` 段确实会生效。
2. **看 MySQL 里到底有几条连接**。查 `information_schema.processlist`，按用户筛一下，能看到连接数和各自的空闲时间。
3. **看它会不会自己放开**。这是最直接的：打几个请求，连接数应该涨上去；停下来等过 `idleTimeoutMillis`，再查应该掉回 0。

实测下来是这样：

```
t=0s    空闲，没请求前          : 0 条连接
        （抓了 4 个页面）
t=2s    刚打完流量             : 4 条连接
t=22s   空闲 20 秒             : 4 条连接
t=47s   空闲 45 秒             : 4 条连接
t=67s   空闲 67 秒（超过 60s）  : 0 条连接

```

空闲连接能被彻底放掉，说明池子不会再给 MySQL 留下「可以超时」的东西。

## 四、改法

```
"pool": {
  "min": 0,
  "max": 10,
  "idleTimeoutMillis": 60000
}

```

`min: 0` 是关键——允许池子完全清空。`idleTimeoutMillis` 控制空闲多久释放，这里设 60 秒。`max: 10` 是上限，防止某个时刻突然并发太高把数据库连接占满。

有人会担心 `min: 0` 会导致每次请求都重新建连接、变慢。对这个博客这种低流量场景，建立连接的开销完全可以忽略（本地 MySQL 上就是微秒级），换来的是再也不会因为空闲被掐。

## 五、一个小教训

中间排查时还踩了一个时间上的坑：用 `docker logs --since` 过滤日志，看到的错误数量对不上，差点误判成「改了以后还在报」。原因是这个参数里不带时区的字符串，Docker 会按守护进程的**本地时区**去解析，而服务器是 JST（UTC+9），于是时间窗口整体偏了 9 小时，把改动之前的错误也捞了进来。

用 `docker logs -t` 带上每个条目的时间戳再核对，才是可靠的。

## 六、小结

这类问题的坑在于：它不是「坏了」，而是「默认值跟你的使用场景不匹配」。连接池的默认 `min: 2` 对高流量服务是合理的——保持几条热连接能省掉建连开销；对一天没几个访客的博客就变成了定时炸弹。

所以看到一个反复出现、又很有规律的错误，先别急着改代码，去看看默认配置。