记录一次MySQL数据库故障
- 2025-08-14 19:07:00
- 丁国栋
- 原创 11
早上收到告警一个监控项无法访问需要处理。
这个监控项是一个URL,通过访问这个URL对应的应用日志确定原因是数据库无法连接。
根据URL查询到所在机器,查询到数据库服务确实中断,而中断的原因是由于 Ubuntu 的自动更新导致 MySQL 数据库升级(这点可以根据mysql进程的进程树可以得到确认 pstree -asp <pid>),而 mysql 软件包升级一直处于不明确的状态,即无法判断它是在升级中还是失败了没有退出。
首先这个服务是一个多租户的SAAS服务,服务等级低,但也有一定的免费用户在使用,也需要尽快恢复。
mysql 软件包升级之所以没有结束,是因为MySQL 数据库实例的数据库数量非常大,根据 MySQL 数据目录统计分析约 12400 个数据库,而每个数据库约 384 个表,数据总量约 600GB。这么大的数据量启动是非常费时间和消耗内存的。
Ubuntu 自动更新没有关闭是导致这个问题的直接原因,而数据库实例中的数据库数量较多是导致软件包升级失败和无法恢复的根本原因。
由于这个数据库在故障时间之前还可以正常运行,所以第一时间是想恢复服务,因此尝试重新启动数据库服务,结果发现很长时间没有反应,继续等待了2小时左右,发现无法启动,查看 dmesg 找到报错原因是OOM。
这个服务器是8C32GB的Microsoft Azure虚拟机,数据库单独一个数据盘,大小是1TB,剩余空间在 300GB 以上,因此磁盘不需要升级,只需要升级内存,从 D8ds_v5 升级到 E8ads_v5,8C64GB。升级实例规格(Size)后系统会自动重启,因此在操作前先把数据库服务设置为不自动启动。
因为软件包升级过程中失败,所以需要先处理升级失败的问题,为了提高升级速度,对mysql进行备份:
mv /etc/mysql /etc/mysql.bak mv /var/lib/mysql /var/lib/mysql.bak # 如果是修改了数据库的数据目录,则可以删除链接文件 rm -i /var/lib/mysql mv /var/log/mysql /var/log/mysql.bak
注:这个过程还忽略了一个重点,当前 Ubuntu 系统启用了 AppArmor而且修改了数据库的数据目录,因此 /etc/apparmor.d/usr.sbin.mysqld 也需要备份,否则数据库可能无法启动。
通过查询 dpkg -l|grep mysql 卸载mysql-server和mysql-server-8.0包(升级失败状态为Unknown、halF-conf)然后重新安装,这样可以保证软件包升级没问题。
root@azpm1:~# dpkg -l|grep mysql ii mysql-client-8.0 8.0.43-0ubuntu0.24.04.1 amd64 MySQL database client binaries ii mysql-client-core-8.0 8.0.43-0ubuntu0.24.04.1 amd64 MySQL database core client binaries ii mysql-common 5.8+1.1.0build1 all MySQL database common files, e.g. /etc/mysql/my.cnf iU mysql-server 8.0.43-0ubuntu0.24.04.1 all MySQL database server (metapackage depending on the latest version) iF mysql-server-8.0 8.0.43-0ubuntu0.24.04.1 amd64 MySQL database server binaries and system database setup ii mysql-server-core-8.0 8.0.43-0ubuntu0.24.04.1 amd64 MySQL database server binaries ii php7.4-mysql 1:7.4.33-14+ubuntu24.04.1+deb.sury.org+1 amd64 MySQL module for PHP root@azpm1:~#在恢复数据和权限以后,接下来就是启动的漫长等待了。
2025-08-14T04:28:35.454197Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.43-0ubuntu0.24.04.1) starting as process 1178 2025-08-14T04:28:35.556305Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2025-08-14T04:42:31.304819Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended. 2025-08-14T06:28:23.856995Z 4 [System] [MY-013381] [Server] Server upgrade from '80042' to '80043' started. 2025-08-14T06:47:02.292187Z 4 [System] [MY-013381] [Server] Server upgrade from '80042' to '80043' completed. 2025-08-14T06:47:02.446150Z 0 [System] [MY-010229] [Server] Starting XA crash recovery... 2025-08-14T06:47:02.448853Z 0 [System] [MY-010232] [Server] XA crash recovery finished. 2025-08-14T06:47:03.104582Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed. 2025-08-14T06:47:03.104622Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel. 2025-08-14T06:47:03.352929Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.43-0ubuntu0.24.04.1' socket: '/var/run/mysqld/mysqld.sock' port: 3306 (Ubuntu). 2025-08-14T06:47:03.383266Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '127.0.0.1' port: 33060, socket: /var/run/mysqld/mysqlx.sock
在经过2小时20分钟终于等到启动,喜大普奔。这个过程是幸运的,首先是这个数据库是低优先级业务,用户量不大,造成影响较小,有足够的时间来处理,压力没有那么大,但尽管如此,面对不不确定性,还是做好了恢复和迁移用户数据的最坏打算,如果这个数据库无法启动,就恢复活跃用户数据到新数据库,当然这个是最不希望的,还是希望能把原数据库修复好。
在本例中,数据库内存的使用峰值出现在 InnoDB初始化之后,Server升级之前,达到了 42GB左右,超出了原来的 32 GB内存。为了确保启动过程中不发生OOM,在升级内存后又设置了 64GB 的交换空间。在Server 升级之后内存降低 16GB,启动成功后内存为 19GB。但升级上来的64GB内存不能再降低了,因为要确保数据库能正常启动。
总结
- 单兵运维的挑战性总是存在。一个人维护就是这样的风险,事情多了忙不过来。
- 在日常运维工作中需要定期盘点运维资产,对于一些没有监控的项目要定期检查它工作状态和数据情况。对于一些老系统没有应用最佳实践的需要额外注意填坑,例如本例的禁用Ubuntu自动更新。
- 做好容量规划。例如本例中数据库被用于多租户的SaaS服务,每一个租户单独一个数据库,就必须要考虑数据库的容量。如果超过一定数量比如4000、5000就需要将服务进行分片,把新租户分配到新的服务器。
- 对于不活跃的用户及时归档处理,热数据放数据库中,冷数据移除出数据库。
- 在本次故障处理中以前积累的笔记和AI帮了大忙,还有群里的运维小伙伴们提供了很多的情绪价值。笔记主要是一些操作,涉及到权限,例如
dpkg --purge mysql-server-8.0
、chown -h mysql:mysql /var/lib/mysql
以及 针对AppArmor 的设置。AI主要是MySQL 内存估算和 MySQL 启动时间的估算等方面。 - 面对一些知识盲区和不确定性,做好最坏的打算,提前准备预案。
附 AI对于MySQL 内存估算和启动时间估算的回答,有一定的参考意义。
MySQL 默认配置下支持15,000个数据库的内存需求估算
在MySQL默认配置下支持15,000个数据库的内存需求主要取决于以下几个因素:
主要内存消耗点
- 数据字典缓存:每个数据库在内存中都有一定的元数据开销
- 表缓存:即使表是空的,每个表也会占用一些内存
- 连接缓冲:取决于并发连接数
- 全局缓冲池:如InnoDB缓冲池
粗略估算
在默认配置下:
- 每个数据库的元数据开销约为50-100KB
- 如果每个数据库有少量表(如5-10个),每个表额外增加约10-20KB
对于15,000个数据库:
- 仅数据库元数据:15,000 × 100KB ≈ 1.5GB
- 如果每个数据库有5个表:15,000 × 5 × 20KB ≈ 1.5GB
- 基本缓冲区和连接内存:至少2-4GB
推荐最小内存
建议至少8-16GB内存来支持15,000个数据库的MySQL实例,这是在默认配置下的保守估计。
注意事项
- 实际需求会根据每个数据库中的表数量、表结构复杂度而显著增加
- 如果数据库中有大量表或复杂结构,内存需求可能呈指数增长
- 生产环境建议进行实际测试,监控内存使用情况
- 可以考虑调整
table_definition_cache
和table_open_cache
等参数来优化内存使用
对于如此大规模的数据库数量,建议考虑分片或使用多个MySQL实例来分担负载。
MySQL 服务器启动时间估算(12,400个数据库 × 385个表)
对于拥有12,400个数据库、每个数据库385个表的MySQL服务器(8核64GB内存),启动时间会显著延长,主要受以下因素影响:
关键影响因素
- 表定义加载:需要加载约4,774,000个表(12,400×385)的元数据
- 存储引擎初始化:特别是InnoDB需要初始化数据字典
- 缓冲池预热:64GB内存中分配给InnoDB缓冲池的部分需要初始化
- 硬件性能:尤其是存储I/O性能(SSD/NVMe vs HDD)
时间估算(基于经验值)
组件 | 时间估算 |
---|---|
核心服务启动 | 10-30秒 |
数据库目录扫描 | 2-5分钟 |
每个表加载时间(~4.7M表) | 主要耗时部分 |
缓冲池初始化 | 1-3分钟 |
总启动时间预估:
- HDD存储:6-12小时或更长(可能因I/O瓶颈更久)
- SATA SSD:2-5小时
- NVMe SSD:1-3小时
- 优化后的NVMe阵列:30分钟-2小时
配置优化建议
-
调整关键参数:
table_definition_cache=600000 # 足够容纳所有表定义 table_open_cache=400000 # 大型表缓存 open_files_limit=500000 # 高文件描述符限制 innodb_buffer_pool_size=48G # 分配约75%内存 innodb_buffer_pool_instances=8 # 匹配CPU核心数
-
启动加速技巧:
- 使用
innodb_buffer_pool_load_at_startup=0
跳过缓冲池预热 - 考虑将表定义缓存(
.frm
文件)放在RAM disk上 - 确保
innodb_force_recovery=0
(正常启动模式)
- 使用
-
架构建议:
- 这种规模的数据库应考虑分片处理
- 评估是否真的需要保持所有数据库在线(冷热数据分离)
监控建议
启动时监控:
SHOW STATUS LIKE 'Uptime';
SHOW STATUS LIKE 'Open%';
SHOW ENGINE INNODB STATUS;
这种极端配置下的MySQL启动本质上是一个I/O密集型操作,存储性能是决定性因素。
--