Day 67

博客挂了两天

打开自己的博客,等来的不是熟悉的首页,而是一行冷冰冰的英文:

Error establishing a database connection

我什么都没改过。上一篇发文的时候还好好的。这是故事的起点——从这行报错开始,我花了将近一天时间,穿过四层真相,最后在一个”孤儿进程”手里找回了我的网站。

一、现场勘查

我的博客跑在一台 1.7G 内存的阿里云小机器上,宝塔面板 + Docker Compose 部署的 WordPress + MySQL 8.0,数据目录挂载在宿主机,容器整整十四个月没有动过。

docker compose logs 拉出来,现场是这样的:

wordpress-1 | 1.162.146.33 - - [14/Oct/2026:00:20:19 +0000] "GET / HTTP/1.1" 500 2808 "..."
wordpress-1 | 138.246.253.24 - - [14/Oct/2026:00:22:04 +0000] "GET /robots.txt HTTP/1.1" 500 2808 "..."
wordpress-1 | 45.156.128.15 - - [14/Oct/2026:00:24:26 +0000] "GET / HTTP/1.1" 500 2808 "..."

所有请求——首页、登录页、robots.txt——一律 500,而且响应体大小清一色 2808 字节。完全一致的体积说明大家在看同一张错误页:数据库连不上。

再看 MySQL 侧,更诡异的一幕:

[InnoDB] InnoDB initialization has started.
[InnoDB] InnoDB initialization has ended.
[Server] Starting XA crash recovery...
[Server] XA crash recovery finished.
[Server] /usr/sbin/mysqld: ready for connections.

这套”开机广播”每隔几秒就重演一遍。XA crash recovery 只在数据库上次没有正常关闭时出现——它不是在重启,是在”复活”,而且复活不了几秒。

二、第一层真相:它不是挂了,是在死循环

$ docker ps -a | grep db
7ec82916ca6d   mysql:8.0   ...   Restarting (137) 41 seconds ago   dk_wordpress-db-1

Restarting (137)。137 = 128 + 9,退出码 9 是 SIGKILL——进程被人一枪打死,不是自己崩的。这两者的区别很关键:MySQL 自己崩溃会留遗书(错误日志里会有 InnoDB 错误、断言失败等等),而我的日志里每次死亡都是上一秒还 ready for connections、下一秒直接消失,干干净净。

一声不吭的死,等于外部击杀。谁会 SIGKILL 一个数据库?头号嫌疑人:内核 OOM killer。

三、排除法:三个”不是我”

怀疑归怀疑,破案要证据。我先排除了几个方向:

配置被动过? 没有。容器十四个月没重建,.env、数据卷、wp-config.php 无人动过。这和”我一直用得好好的”吻合。

容器有内存上限? docker inspect 显示 Memory=0,没有套笼子。

systemd-oomd 或定时任务在杀进程? 系统是 CentOS 7,根本没有 oomd;crontab 里只有一个每天跑一次的宝塔日志任务,对不上”几秒死一次”的节奏。

最迷惑的是 dmesg | grep oom 居然是空的。内核 OOM 杀人必然会留 Out of memory: Killed process ... 的记录,一条都没有。(后来才明白:dmesg 的环形缓冲区可能早就滚没了、或被清理脚本清过——**要看权威记录,得去 /var/log/messages**。)

四、铁证:案发在三天前的深夜

$ grep -iE 'out of memory|killed process' /var/log/messages | tail
Oct 12 23:11:23 xxx kernel: AliYunDunMonito invoked oom-killer: ...
Oct 12 23:11:23 xxx kernel: Out of memory: Kill process 10188 (ib_pg_flush_co) score 151 ...
Oct 12 23:11:23 xxx kernel: Killed process 10188 (ib_pg_flush_co), UID 999, anon-rss:388380kB
Oct 12 23:11:23 xxx kernel: Killed process 10205 (ib_srv_mon), UID 999, ...

10 月 12 日 23:11:23——我最后一次正常发文正是那之前。ib_pg_flush_co、ib_srv_mon 都是 mysqld 的 InnoDB 内部线程,UID 999 是容器里的 mysql 用户。凶手、时间、死者全齐了。

作案动机也清楚:这台 1.7G 的机器上,宝塔面板、阿里云的安骑士监控(AliYunDunMonito,引爆 OOM 的正是它申请内存的动作)、Apache、Docker 各占一份,MySQL 8 默认配置光 performance_schema 加 buffer pool 起步就要 500M+。内存不够分的,内核只好杀最肥的那个。

打个比方:物理内存是桌面,swap 是桌下的柜子。我的桌面早就摆满了,柜子里也塞了半柜;MySQL 每次开工要一次性在桌上摊开 500M 的材料,桌上柜下都周转不开,于是它每次开工就被清场。

这不是”改了什么”导致的事故,是十四个月的缓慢消耗——数据慢慢涨、插件慢慢多、公网机器人的流量只增不减,水位悄悄爬过了警戒线。

五、第一次修复:swap + 瘦身

确认病因后动手,三步:

# 1. 冷备份(数据库停机状态下整目录拷贝,423M,全站家底)
docker compose stop db
cp -a ${DATA_PATH}/db /root/db_backup_20261014

# 2. swap 从 1G 扩到 3G
dd if=/dev/zero of=/swapfile2 bs=1M count=2048
chmod 600 /swapfile2 && mkswap /swapfile2 && swapon /swapfile2

# 3. 给 MySQL 瘦身,compose 里加:
command:
  - --innodb-buffer-pool-size=64M
  - --performance-schema=OFF
  - --max-connections=30

三个参数分别管:数据缓存(我的库一共不到 500M,64M 装下常用部分绰绰有余)、内部自我监控(个人博客用不上,白吃三四百 M)、并发连接上限(30 个同时连接对个人博客是天文数字)。

docker compose up -d db,结果——死得慢了。从”每几秒死一次”变成”几分钟死一次”。有进步,但没根治。

六、第二层真相:锁

再次翻日志,这次刷屏的换了内容:

[ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
[ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11
[ERROR] [MY-012574] [InnoDB] Unable to lock ./ibdata1 error: 11

ibdata1 是 InnoDB 的核心数据文件,MySQL 启动时第一件事就是给它上锁。error 11 = 文件已被别人锁着——有另一个 mysqld 进程攥着我的数据库不放。容器里这个每秒重试一次,永远抢不到。

谁是”另一个”?在宿主机上把所有 mysqld 进程和当前容器的进程对照着看:

$ ps aux | grep mysqld
root   1244 ... /www/server/mysql/bin/mysqld_safe ...    # 宝塔自己装的 MySQL,无关
mysql  1956 ... /www/server/mysql/bin/mysqld ...         # 同上
polkitd 5318 ... mysqld                                   # ← 裸 mysqld,369M,不属于任何容器
polkitd 8728 ... mysqld --innodb-buffer-pool-size=64M ... # 当前容器里的正主

$ docker top dk_wordpress-db-1
PID   CMD
8728  mysqld --innodb-buffer-pool-size=64M ...

PID 5318,一个不带任何参数的裸 mysqld——旧容器默认配置的样子。它是这两天反复强杀过程中遗留的孤儿进程:容器没了,进程还在,数据文件的锁也就一直没释放。

kill -9 5318

三十秒后,正在每秒重试抢锁的正主立刻接管,InnoDB initialization 顺利走完。SHOW DATABASES 列出完好的 wordpress_db,curl 状态码:

200

评论

我有话要说……

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注