ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL 启动失败 code=exited status=1 排查指南:从日志定位到 InnoDB 恢复

MySQL 启动失败 code=exited status=1 排查指南:从日志定位到 InnoDB 恢复 周六下午最怕接到这类信息“MySQL 挂了”、“服务起不来了”。登录服务器敲下systemctl status mysql屏幕上一行Active: failed (Result: exit-code)下面跟着Process: 1234 ExecStart/usr/sbin/mysqld ... (codeexited, status1/FAILURE)。说实话干了这么多年运维看到这个报错我一点都不慌因为类似的工单截图几乎每周都能见到。真正要解决的问题不是“怎么把服务拉起来”而是“为什么它起不来”。codeexited, status1FAILURE这个报错最坑的地方在于它只告诉你进程崩了、退出码是1但完全不告诉你崩溃的原因。同样一个报错可能是配置文件写错一个字母可能是数据目录权限不对也可能是磁盘满了、端口被占了、甚至是 InnoDB 数据文件损坏。盲目去执行systemctl restart mysql大概率只会看到同样的失败结果然后陷入“重启-失败-再重启”的死循环。这篇文章我把这些年排查MySQL 启动失败 (codeexited, status1FAILURE)的经验完整整理出来从错误日志定位、高频率原因排查到每种场景的具体修复命令再到 InnoDB 数据损坏的恢复方案全部按实际动手顺序写。不管你已经踩过坑还是第一次遇到照着这个流程走一遍大多数情况都能在十分钟内搞定。1. 先搞懂报错在说什么status1 只是结果不是原因1.1 systemd 的退出码到底代表什么在 Linux 上MySQL 通常由 systemd 托管。systemctl status mysql的输出里codeexited表示 ExecStart 指定的进程已经退出status1是进程退出时返回的状态码。对 MySQL 来说退出码1是一个通用失败标志它代表“mysqld 在启动过程中遇到了致命错误主动退出了”。这里有个关键点systemd 只负责监控和管理进程的生死它不负责解释进程为什么退出。所以看到status1时正确的反应不是去翻 systemd 的文档而是立刻去查 MySQL 自己的错误日志。另外也要理解一个隐含信息MySQL 在启动阶段就崩了这说明问题大概率出在初始化流程里而不是运行时的某个慢查询或锁竞争。顺带提一个容易忽略的细节如果你用systemctl start mysql后立刻看systemctl status mysql看到的status1可能还伴随main process exited。这种情况下不要只截图这一行发给同事完整日志才是别人能帮你判断的关键信息。1.2 第一步永远是从这里看MySQL 错误日志排查任何 MySQL 启动问题第一件事不是改配置而是找到错误日志。日志路径因安装方式和发行版不同会有所差异下面这些是常见的候选位置/var/log/mysql/error.logDebian/Ubuntu 用 apt 安装的 MySQL 或 MariaDB 默认路径。/var/log/mysqld.logCentOS/RHEL 用官方 rpm 包安装时的默认路径。/var/lib/mysql/*.err部分源码编译或自定义 datadir 时的默认位置。journalctl -u mysql -n 50 --no-pager如果日志输出被 systemd 接管会在这里。查看日志时不要只翻最后两行建议先看最后 50 到 100 行。通常报错的真正原因会以[ERROR]或[FATAL]开头有时还会伴随 InnoDB 的提示。比如经典的权限问题会在日志里出现2024-01-01T00:00:00.123456Z 0 [ERROR] [MY-000000] [InnoDB] Operating system error number 13 in a file operation. 2024-01-01T00:00:00.123456Z 0 [ERROR] [MY-000000] [InnoDB] The error means mysql does not have the access rights to the directory.这两行基本可以锁定是权限问题。类似的还有磁盘满、端口占用等错误日志里通常会有明确的关键词。老规矩看到status1先别慌先打开错误日志看最后几十行八成问题当场就能定位。1.3 日志路径找不到怎么办三条命令搜出真实位置有时候日志不在默认路径连journalctl里都只有 systemd 的报错没有 MySQL 自己的日志输出。这时候可以靠三条命令定位# 查看 systemd 服务定义里 ExecStart 的完整参数 systemctl cat mysql # 让 mysqld 自己打印相关路径配置 mysqld --verbose --help 2/dev/null | grep -E log-error|datadir|socket # 实在找不到全盘搜一下 .err 文件 find / -name *.err 2/dev/null第一条命令能看出 mysqld 被 systemd 启动时带了哪些参数比如有没有显式指定--log-error。第二条命令会让 mysqld 列出编译时的默认配置。第三条是最后的办法虽然全盘 find 有点慢但通常能直接把.err文件挖出来。另外要注意有些发行版把 MySQL 的服务名注册成了mysqldCentOS 的 rpm 包默认就叫这个有些叫mysql还有的装了 MariaDB 但服务名是mariadb。用systemctl list-unit-files | grep -i -E mysql|mariadb先确认服务名再执行状态查询省得对着一个不存在的服务白忙活。2. 高频原因与快速定位五个方向先自查2.1 配置文件写错第一步就先验证这个我经手过的启动失败案例里配置文件语法错误和路径写错占了将近一半。常见的有这么几类变量名拼写错误比如character-set-serverr多打了一个 rMySQL 直接不认。引号用了中文全角引号配置解析失败。datadir路径后带了多余空格导致数据目录找不到。启用了不存在的插件或者init_file指向了不存在的脚本。对于这种问题MySQL 提供了一个非常好用的自检命令mysqld --validate-config这个命令会读取默认配置文件并检查语法如果有问题会在 stderr 输出具体的报错信息。如果用了自定义配置文件指定一下mysqld --defaults-file/etc/my.cnf --validate-config如果输出OK这种提示部分版本没有输出说明配置语法层面没问题可以跳过这一步去查别的方向。如果报错通常会精确到配置文件的行号和具体原因比如2024-01-01T00:00:00.123456Z 0 [ERROR] [MY-000000] [Server] unknown variable character-set-serverrutf8mb4看到这种提示去配置文件里改掉对应行即可。验证通过后记得systemctl daemon-reload再重启服务因为配置文件的变更 systemd 可能会缓存旧定义。2.2 数据目录权限运行用户必须精确匹配MySQL 在启动时会以mysql用户身份去读写数据目录如果数据目录的属主和权限不对进程直接退出。这种问题在服务器重启后更容易暴露出来——比如之前有人用 root 初始化了数据目录或者发生了误操作把/var/lib/mysql的属主改了。检查方法很简单ls -ld /var/lib/mysql正常情况下属主应该是mysql:mysql权限是700或750。如果看到root:root或者干脆没有权限那就修复chown -R mysql:mysql /var/lib/mysql这里有个容易忽略的坑不仅数据目录本身它的父目录层级也要保证可访问。比如/var/lib/mysql的父目录/var/lib不能没有执行权限否则即便子目录权限正确mysql 用户也进不去。错误日志里体现出来的就是Operating system error number 13。改完权限之后再次启动多半就好了。还有一个细节如果之前是用 root 手动跑过mysqld可能会在数据目录下生成 root 属主的临时文件比如*.pid或者 socket 文件。chown -R一把梭通常能解决问题但最好先清理掉这些脏文件再启动。2.3 端口被占用八成是之前的实例没退干净3306 是 MySQL 的默认端口也是最容易被误伤的点。常见场景包括同时安装了多个 MySQL 实例、开发环境里跑着 Docker 容器占用了 3306、或者之前非正常 kill 掉的 mysqld 还在监听端口虽然很少见。检查命令ss -lntp | grep 3306 netstat -tlnp | grep 3306 # 某些系统需要安装 net-tools lsof -i :3306如果看到某个进程占用了 3306先确认进程是谁。如果确实是残留的 mysqld可以kill -9 PID如果占用者不是 MySQL而是别的服务那么要么改 MySQL 的端口要么让占用方挪位置。临时改端口可以在/etc/my.cnf的[mysqld]段加port3307改完之后启动验证。注意客户端连接时也要指定-P3307否则连不上。2.4 磁盘满 vs inode 耗尽df 要看两个指标磁盘满了会导致日志写不进去InnoDB 在启动阶段无法完成必要的文件操作进程自然退出。很多人会习惯性看df -h但有时候df -h显示还有空间真正的问题却是 inode 耗尽了。df -h # 查看磁盘空间 df -i # 查看 inode 使用情况如果某个挂载点的 inode 使用率达到 100%即使磁盘还有剩余空间文件系统也没法再创建新文件MySQL 照样起不来。这种情况多见于存放大量小文件的目录比如 binlog 或临时文件目录。处理思路是先清出空间。binlog 占了大量空间的可以临时清理一部分PURGE BINARY LOGS TO mysql-bin.000100;注意binlog 千万不要用 rm 直接删否则 MySQL 的 binlog 索引文件会与实际文件对不上后续可能导致复制中断或者启动异常。详细操作后面会单独说。2.5 安全策略拦截SELinux 和 AppArmor 容易被忽略如果你在 CentOS/RHEL 上把 MySQL 的数据目录挪到了自定义位置比如/data/mysql那么即使权限完全正确SELinux 也可能阻止 mysqld 访问。日志里同样会看到Permission denied但chown和chmod都修过了问题依旧。这种时候可以试试临时关闭 SELinuxsetenforce 0 systemctl start mysql如果能正常启动说明就是 SELinux 策略的问题。这时候别急着永久关闭 SELinux生产环境不建议而是恢复文件的安全上下文restorecon -Rv /data/mysql如果还是不行可以用audit2why查看具体的策略拒绝原因。Ubuntu/Debian 上则有 AppArmor默认配置下 MySQL 只能访问标准路径自定义 datadir 也需要同步修改/etc/apparmor.d/下的规则。这个排查思路比较冷门但遇到权限看着没问题却一直启动失败的情况多半就是它。3. 分场景修复实操从日志到恢复的完整过程3.1 配置文件错误的修复示例从报错行号到正确定位假设我们遇到一个典型的配置错误场景。启动时报错2024-01-01T00:00:00.123456Z 0 [ERROR] [MY-000000] [Server] unknown variable lower_case_table_namesr1错误信息里直接提到了变量名明显是lower_case_table_names被写成了lower_case_table_namesr。去/etc/my.cnf或者/etc/mysql/目录下搜这个关键词grep -rn lower_case_table_names /etc/mysql/找到对应行后改掉重新执行mysqld --validate-config确认语法然后systemctl restart mysql。有些时候报错不会这么明显比如指定了一个不存在的路径2024-01-01T00:00:00.123456Z 0 [ERROR] [MY-000000] [Server] Cant start server : Bind on unix socket: No such file or directory这可能是配置里的 socket 路径对应的目录不存在或者是/var/run/mysqld目录缺失。发行版通常会在安装时创建/var/run/mysqld但有时因为清理服务或者权限问题目录没了。处理mkdir -p /var/run/mysqld chown mysql:mysql /var/run/mysqld这类配置指向的路径不存在问题在日志里常常有明确提示关键是养成先看日志的习惯。3.2 权限问题的标准修复流程chown 之后别忘了验证权限问题是最容易修的一类但也最容易反复。标准流程# 1. 确认运行用户 ps aux | grep mysqld | grep -v grep # 2. 停止服务如果还挂着 systemctl stop mysql # 3. 修复数据目录属主 chown -R mysql:mysql /var/lib/mysql # 4. 修复运行目录 mkdir -p /var/run/mysqld chown mysql:mysql /var/run/mysqld # 5. 重新启动 systemctl start mysql这里有个心得chown -R虽然能解决大部分权限问题但有时候问题不在于数据目录本身而在于 MySQL 初始化时要读写/var/lib/mysql里的临时文件。如果上一次异常退出留下了 root 属主的临时文件务必要一并处理掉。检查方式find /var/lib/mysql -maxdepth 1 -name *.pid -o -name *.sock -ls这些文件如果属主是 root直接删除即可MySQL 启动时会重新创建。3.3 端口占用时的应急切换方案先抢回端口还是先换端口端口被占用时取决于占用方的性质。如果占用者是僵尸进程——比如之前用mysqld_safe启动后没有正常关闭的实例——直接杀掉最省事ss -lntp | grep 3306 kill -9 PID systemctl start mysql如果占用者是另一个重要的业务服务不能随便杀那就临时给 MySQL 换个监听端口。在配置文件里改端口后记得确认防火墙和客户端连接参数。有个更稳妥的做法是保留默认端口把占用者挪走但这涉及业务改造不是每次都能快速完成。值得一提的是MySQL 启动时报 Bind on TCP/IP port: Address already in use错误日志里通常直接写明端口。如果你没配置过端口自定义看到这种报错基本就是端口冲突无疑。3.4 磁盘清理与 binlog 正确处理别手贱 rm binlog磁盘满导致的启动失败清理顺序建议如下先看哪个目录占的空间大du -sh /var/lib/mysql/* | sort -hr | head如果 binlog 占大头进入 MySQL如果还能连上的话执行PURGE BINARY LOGS TO mysql-bin.000100;如果 MySQL 已经起不来只能通过配置文件临时禁用 binlog 来启动[mysqld] skip-log-bin启动后先备份再考虑磁盘扩容或迁移。关于 binlog 为什么不能直接删MySQL 的mysql-bin.index文件中记录了 binlog 文件列表。你手动删除文件后索引文件与实际文件不一致MySQL 启动时或后续清理时可能报错严重时可能导致数据复制中断。同理也不建议直接 truncate 大文件。正规做法就是PURGE BINARY LOGS或直接配置expire_logs_days让系统自动清理。日志文件本身占用的空间也不容小觑。错误日志经年累月可能长到几 GB配置 logrotate 是标准做法。一个基本的配置示例/var/log/mysql/error.log { daily rotate 7 missingok compress delaycompress notifempty create 640 mysql mysql }把这段放到/etc/logrotate.d/mysql里日志按天轮转保留七天压缩的历史既控制磁盘占用又不丢短期排查线索。4. 更麻烦的情况数据目录损坏与崩溃恢复4.1 为什么 MySQL 会认为自己崩溃了如果日志里出现下面这些关键词问题就比权限和配置要麻烦了InnoDB: Database page corruption InnoDB: Corruption of an InnoDB table InnoDB: Assertion failure in thread ...造成数据页损坏的原因很多可能是突然断电、硬件故障、非正常 kill -9或者磁盘写入过程中的 I/O 错误。InnoDB 在启动时会做崩溃恢复利用 redo log 将已提交但尚未落盘的事务重新应用把未提交的事务回滚。如果 redo log 对应的数据页本身已经损坏恢复过程就会卡住或直接报错。遇到这类问题第一原则是不要反复重启。每次启动都可能触发崩溃恢复过程而损坏的页可能在恢复中被进一步覆盖。正确的姿势是先备份整个数据目录至少备份 ibdata1、redo log 和关键表空间再做后续恢复尝试。4.2 innodb_force_recovery 的正确用法1 到 6 分别代表什么MySQL 提供了innodb_force_recovery参数来强制启动级别从 1 到 6数字越大越激进越可能导致数据丢失。各个级别的作用级别作用风险1忽略损坏页强制继续可能读到坏页上的数据2阻止后台线程运行无法做正常后台刷新3不执行事务回滚可能有未回滚的脏数据4不执行 buffer pool 预读和合并可能丢失部分已提交事务5不扫描 undo log未完成的事务无法正确回滚6不执行 redo log 前滚已提交但未落盘的数据可能丢失实际使用中建议从 1 开始逐步尝试能启动就别调更高的级别。使用方法是在/etc/my.cnf的[mysqld]段加innodb_force_recovery1然后尝试启动。如果 1 不行就改成 2、3以此类推。注意force_recovery 级别大于 0 时MySQL 通常以只读方式启动这是正常现象。此时能连上实例做的第一件事应该是导出数据mysqldump -u root -p --all-databases --single-transaction --routines --triggers backup.sql导出成功后把innodb_force_recovery参数去掉停止服务备份现有数据目录然后重建一个新的空数据目录并初始化mv /var/lib/mysql /var/lib/mysql.bak mkdir -p /var/lib/mysql chown mysql:mysql /var/lib/mysql mysqld --initialize-insecure systemctl start mysql最后导入刚才的备份mysql -u root -p backup.sql这一套流程下来基本上能把数据损失控制在最小范围内。多说一句innodb_force_recovery是临时救急手段不是长期运行配置。留着它启动虽然在恢复模式下可以查询但写入会被禁止或导致数据不一致绝不能作为正式方案长期跑。4.3 其他启动阶段的问题redo log 大小与版本差异还有一种不算少见的情况你改了innodb_log_file_size但 MySQL 版本是 5.7 或更早。老版本里 redo log 文件是固定的ib_logfile0和ib_logfile1修改大小后如果在启动时检测到文件大小不匹配可能报错并拒绝启动。旧版本处理方式是把这两个文件删掉再启动MySQL 会重新生成。到了 MySQL 8.0redo log 从ib_logfile*改成了#innodb_redo目录并且大小调整后可以自动重扩这个问题基本消失。如果你还在维护 5.7 的老实例遇到类似报错要想起这个原因。另一个版本差异点是初始化命令。MySQL 5.7 及以后用mysqld --initialize或mysqld --initialize-insecure初始化 root 无密码更早的版本用mysql_install_db。如果你在 5.7 上误用了mysql_install_db启动时也可能报错。遇到Cant find error-message file之类的提示多半是初始化步骤没做对。5. 常见问题速查与避坑经验5.1 错误日志关键词与对应处理速查表下面这个表是我实际排查时最常用的对照表。遇到报错先对号入座能省大量时间错误日志关键词大概率原因优先处理方向unknown variable配置文件变量名写错mysqld --validate-config定位并修改Operating system error number 13数据目录或文件权限不对chown -R mysql:mysql数据目录Address already in use端口被占用ss -lntp查占用进程kill 或改端口No space left on device磁盘满或 inode 耗尽df -h、df -i清理 binlog/日志Permission deniedSELinux 场景SELinux/AppArmor 策略拦截restorecon或临时setenforce 0验证Corruption of an InnoDB table数据页损坏用innodb_force_recovery逐级尝试恢复Cant start server : Bind on unix socketsocket 目录缺失或权限错误重建/var/run/mysqld目录Cant find error-message file初始化未完成或路径配置错误确认--initialize是否执行成功这个表覆盖了我遇到过的九成情况。剩下的一成通常需要结合完整的日志上下文去分析但顺着错误日志本身一般也能找到线索。5.2 几个值得记住的实测细节第一systemctl reset-failed mysql是个容易被忽略的命令。当服务因为启动失败进入failed状态后systemd 会记住这个状态。有时候你明明已经修复了配置或权限重新执行systemctl start mysql却还是失败原因之一就是 systemd 认为服务仍然处于 failed 状态需要先重置systemctl reset-failed mysql systemctl start mysql第二修改my.cnf之后不是所有参数都要重启才能生效但凡是涉及路径、端口、InnoDB 初始化相关的参数基本都需要完全重启。重启前执行systemctl daemon-reload是个好习惯确保 systemd 没有缓存旧的单元定义。第三不要有事没事就kill -9 mysqld。正常情况下应该用mysqladmin shutdown或systemctl stop mysql优雅关闭。kill -9属于物理断电级别强制终止后 InnoDB 下次启动必然要做崩溃恢复虽然大部分时候能自动恢复但频繁这样操作会显著增加数据损坏的概率。第四生产环境如果有条件优先考虑给 MySQL 单独挂载一块磁盘或至少单独一个分区做数据目录。这不仅是性能考虑更重要的是避免因为/根分区写满波及 MySQL 的启动。遇到过太多次因为别的服务写满了根分区结果 MySQL 跟着遭殃的情况。5.3 开机自启与后续预防别让问题重复发生服务恢复后记得确认 MySQL 是否设置了开机自启systemctl enable mysql systemctl is-enabled mysql如果输出enabled开机自启没问题。如果是disabled或static用systemctl enable mysql开启。这一步容易被忽略很多人重启服务器后发现 MySQL 没起来还以为又坏了实际上只是开机启动没配好。预防启动失败最有效的手段是监控。不用上什么重量级系统一个简单的定时脚本检查进程和端口就够#!/bin/bash if ! pgrep -x mysqld /dev/null; then echo $(date) MySQL is down, attempting restart /var/log/mysql-restart.log systemctl restart mysql fi放到 crontab 里每分钟执行一次。注意这个脚本只能兜底不能替代排查。如果 MySQL 是因为数据损坏起不来的无限重启只会加重问题。更好的组合是脚本检查到失败时把错误日志的末尾几行发到监控群这样有人能及时发现并介入处理。最后再提一个容易被 MySQL 启动失败问题掩盖的场景如果服务反复启动失败但日志里每次报错都不一样先怀疑是不是机器本身有硬件问题尤其是磁盘。跑一遍dmesg | grep -i error和smartctl -a /dev/sda把硬件因素排除掉再回头查软件配置。很多时候 MySQL 只是替硬盘背了锅。
返回列表