最近好几台服务器上的 MySQL 都出现过同样一个现象:执行systemctl start mysql,提示失败,systemctl status mysql一查,冒出来一行Process: 12345 ExecStart=/usr/sbin/mysqld ... (code=exited, status=1/FAILURE)。很多朋友一看到status=1就慌了,以为数据库崩了、数据丢了,其实绝大多数情况都只是启动阶段的一两个小问题。这篇文章就围绕这个报错,把从报错含义、日志定位到具体修复的完整排查过程写清楚,适合刚接触 Linux 运维的开发者、自己搭库的测试环境管理员,以及被这个错误折磨过好几天的同学。我会把我在实际环境里踩过的坑、用过的命令、验证过的思路全部放出来,照着步骤走基本都能解决。
1. 理解 status=1/FAILURE 的含义与常规排查思路
1.1 systemd 日志里 status=1 到底在说什么
要解决这个问题,先得弄明白code=exited, status=1/FAILURE是怎么产生的。你的 MySQL 服务在 systemd 里跑起来时,其实是由 systemd 拉起一个主进程,也就是 mysqld 进程。如果这个进程正常启动,它会一直常驻后台,systemd 看到的返回状态是running;但如果 mysqld 在启动过程中因为某个致命错误自己退出了,进程返回值不是 0,systemd 就会把服务标记为failed,并且把退出代码记下来。
这里的status=1不是 MySQL 自定义的错误码,而是 mysqld 主进程的退出码。退出码为 1,通常意味着初始化过程中遇到了不可恢复的错误,mysqld 主动调用exit(1)结束。至于具体是什么错误,systemd 本身并不知道,它只能告诉你“进程带着状态 1 失败了”。真正的原因,必须去 MySQL 的错误日志里找,这是排查的第一条铁律:别急着改配置,先看日志。
另外,FAILURE是 systemd 对非零退出码的统一标记。有些文章会把它理解成 MySQL 的“严重故障”,其实它只是 systemd 的通用状态标识。所以看到这个状态不要慌,它不能判断数据是否损坏,也无法告诉你问题是出在权限、配置还是磁盘,只是一个“进程没起来”的提示。换句话讲,你离真正的问题,就差一步日志查看的距离。
1.2 启动失败排查的整体路线
我处理这类问题时,习惯按照一个固定的顺序来,避免东翻一下西翻一下浪费时间。整体思路是:先确认服务状态 → 查看错误日志 → 检查基础资源 → 检查配置与权限 → 处理残留进程与冲突 → 修复后验证启动。每一步都有对应的命令和判断依据,下面这张表可以先帮你建立整体感觉。
| 排查步骤 | 主要命令 | 重点观察项 |
|---|---|---|
| 查看服务状态 | systemctl status mysql | 是否 failed,PID 是否存在,日志提示 |
| 查看错误日志 | journalctl -u mysql或tail -f /var/log/mysql/error.log | [ERROR]行及上下文 |
| 检查磁盘空间 | df -h | /或数据目录分区是否 100% |
| 检查端口占用 | ss -lntp | grep 3306 | 3306 是否被其他进程占用 |
| 检查权限属主 | ls -ld /var/lib/mysql | 属主是否为 mysql |
| 检查残留进程 | ps -ef | grep mysqld | 是否存在多个 mysqld 进程 |
| 检查 PID/Socket 文件 | ls -l /var/run/mysqld/ | 是否有陈旧 pid 或 sock 文件 |
| 前台调试启动 | mysqld --user=mysql --console | 看是否有额外错误输出 |
这套流程不需要按部就班严格走完,但大多数情况下,错误日志已经指明了方向,后面的检查只是用来确认和验证。
2. 最常见的三大原因:配置、权限、资源
2.1 配置文件写错的几个典型场景
很多status=1的失败案例,源头就是my.cnf或者my.ini里某个参数写错了。这个错不一定是拼写错误,也可能是路径不对、参数名不合法、或者某个参数支持的版本与你当前的版本不一致。
比如有人把datadir写成了/var/lib/mysql/,注意末尾多了一个斜杠,MySQL 在某些版本下对路径拼接很敏感,启动时找不到数据目录就会直接报错。还有的人把basedir指错了,结果找不到share目录里的errmsg.sys文件,日志里会出现类似Can't open the mysql.plugin table或Can't find messagefile的信息。
另外,MySQL 8.0 里把一些旧参数废弃了,比如query_cache_size、innodb_file_io_threads等。如果你原来是从 5.7 升上来的,配置文件里留着这些旧参数,8.0 会直接拒绝启动。这种情况我在升级场景里见得太多了,日志里会明确说unknown variable 'query_cache_size'。
怎么快速定位配置问题?两个办法。第一个是执行mysqld --verbose --help,这个命令会让 mysqld 打印它实际生效的配置,同时也会在开头列出配置文件读取顺序。第二个是执行mysql --print-defaults,可以快速查看当前生效的参数组合。我建议在修改配置后、重启前,先运行一下mysqld --validate-config,它能在不启动数据库的情况下帮你检查配置语法和参数合法性,虽然它不能发现所有逻辑问题,但至少能挡住大部分低级错误。
2.2 数据目录权限问题与修复命令
权限问题是status=1的另一大来源,尤其是从源码包安装、从其他机器拷贝数据目录、或者用 root 跑过初始化命令的情况。MySQL 的 mysqld 进程通常以mysql用户运行,如果数据目录/var/lib/mysql的属主是 root,或者某些关键文件属主不对,mysqld 启动时访问文件就会触发[ERROR] InnoDB: Operating system error number 13或者[ERROR] Can't read from file.。
错误码 13 对应的就是权限不足(EACCES),这是相当有辨识度的。此时需要把数据目录重新归属给 mysql 用户。命令很简单:
chown -R mysql:mysql /var/lib/mysql如果数据目录不小,这个命令执行时间会长一点,耐心等待。之后还要检查目录权限,一般建议数据目录本身是 755 或 750,里面的.frm、.ibd、ibdata1等文件是 660 或 600。如果之前用 root 手动改过权限,最保险的方式是用chmod -R 660 /var/lib/mysql配合目录chmod 700处理,但要注意目录必须保留执行权限,否则无法进入。
另外,MySQL 的配置文件本身也可能权限过宽。MySQL 5.7 及之后的版本对my.cnf的权限有要求,如果配置文件是 666 或者普通用户可写,mysqld 会拒绝读取,日志里出现World-writable config file '/etc/my.cnf' is ignored。这个信息不容易引起注意,因为它只是忽略配置文件,然后以默认参数启动,导致你设置过的端口、路径全无效,最终也可能启动失败。处理方式是执行chmod 644 /etc/my.cnf。
2.3 磁盘空间与文件句柄不足
别看磁盘空间问题很基础,实际排查里遇到不少。MySQL 写数据或者写重做日志时需要连续分配磁盘空间,当/var/lib/mysql所在分区使用率达到 100%,启动时创建临时文件、扩展表空间都会失败,日志里可能只有一句[ERROR] InnoDB: Error number 28 means 'No space left on device'。代码 28 表示 ENOSPC。
遇到这种情况先执行df -h,确认当前分区剩余空间。如果确实满了,需要找出是什么文件把空间占满了。常见元凶包括 binlog 日志、慢查询日志、以及 core dump 文件。du -sh /var/lib/mysql/*可以帮你快速找到大文件。清理时不要直接删正在写入的 binlog,而是用PURGE BINARY LOGS TO 'mysql-bin.xxx',或者如果实例已经起不来了,那就只能手动清理一些确定不会影响数据安全的日志文件。
文件句柄不足则比较隐蔽。mysqld 需要打开大量文件,尤其是 InnoDB 的表、日志文件、网络连接。操作系统默认的ulimit -n如果是 1024,MySQL 高并发场景下很容易撞墙。启动时可能没有明显错误,但当连接数上来后会出现Too many open files。如果你设置了open_files_limit但没有同步调整 systemd 服务文件里的LimitNOFILE,也会导致配置不生效。检查方式是在启动前执行ulimit -n,对 systemd 服务,可以通过systemctl show mysql查看当前限制,修改服务文件里的LimitNOFILE=65535后执行systemctl daemon-reload。
3. 从日志入手,一分钟定位核心故障
3.1 查看错误日志的正确姿势
错误日志是解决status=1问题的金钥匙,但很多人并不知道该看哪个文件。MySQL 的日志路径不是固定的,取决于配置里log_error参数。在 systemd 下,错误日志通常还会被 journal 捕获,所以你有两个选择。
第一个选择是直接看 journal:
journalctl -u mysql --since "5 minutes ago"这个命令会输出 mysqld 经过 systemd 记录的所有标准输出和错误输出,信息量比较全。如果你刚启动失败,--since "5 minutes ago"能过滤出刚才那次尝试的日志,不会淹没在历史信息里。
第二个选择是看配置文件里指定的错误日志:
tail -n 100 /var/log/mysql/error.log如果不知道路径,可以通过grep -R "log_error" /etc/my.cnf /etc/mysql/来找。实在找不到,还可以用mysqld --verbose --help | grep log-error来查看默认路径。我个人比较推荐直接把错误日志配置成固定路径,比如/var/log/mysql/error.log,这样不管哪个环境都不会迷路。
看日志时不要只盯着[ERROR]这一行,错误信息往往在[ERROR]之前的几行里已经埋下了伏笔。比如 InnoDB 经常先打印一些[Warning]或者[Note],后面才出现致命的[ERROR] Aborting。把上下文一起看,才能快速判断是权限、磁盘还是崩溃恢复问题。
3.2 常见错误日志特征与对应解决办法
我把自己遇到最多的日志形态整理成了一张对照表,每一类都对应不同的处理方案。这张表基本覆盖了status=1的日常场景。
| 日志关键词 | 可能原因 | 推荐处理方式 |
|---|---|---|
Can't find messagefile | basedir 或 share 路径配置错误 | 检查 basedir 配置,确认安装目录完整 |
Can't open the mysql.plugin table | 系统库损坏或初始化不完整 | 尝试以innodb_force_recovery启动并备份数据 |
Operating system error number 13 | 数据目录权限不足 | chown -R mysql:mysql并检查目录权限 |
Can't start server: Bind on TCP/IP port: Address already in use | 3306 端口被占 | ss -lntp查看占用进程,停掉或改端口 |
Can't start server: Bind on unix socket: Address already in use | socket 文件冲突 | 删除陈旧 socket 文件或调整 socket 路径 |
Another process with pid | pid 文件指向一个不存在或残留的进程 | 使用ps确认后删除旧 pid 文件 |
InnoDB: Error number 28 means 'No space left on device' | 磁盘空间不足 | 清理文件或扩容分区,确认数据目录所在分区多余空间 |
InnoDB: Database page corruption | 数据页损坏 | 从备份恢复,或使用innodb_force_recovery抢救数据 |
[ERROR] unknown variable | 配置文件里有当前版本不支持的参数 | 注释或删除对应配置,重新校验 |
Can't create PID file | pid 目录不存在或权限不足 | 创建/var/run/mysqld并改属主,或调整 pid 路径 |
注意,很多情况下日志里只会出现[ERROR] Aborting,这并不意味着原因不明。“Aborting”是 mysqld 经历初始化失败后的统一收尾动作,它前面一定有更具体的报错,所以看到Aborting反而要继续向上翻日志找本次启动的第一条[ERROR]。
4. 几个让人头疼的隐性坑:残留进程、socket 冲突、InnoDB 损坏
4.1 残留进程与 pid 文件冲突
有次帮同事排查一个 MySQL 起不来的问题,日志里来回出现Another process with pid 29219 is using data directory,但ps里怎么都找不到这个进程。最后发现,数据目录里有一个陈旧的mysqld.pid文件,里面写着早已不存在的进程号。mysqld 校验 pid 文件时发现文件存在、且里面记录的进程不存在,按说应该正常清理覆盖,但某些版本在 pid 文件权限异常或者目录不可写时,就会认为有别的实例在跑,直接拒绝启动。
这种问题的典型特征是:日志里出现 pid 信息,但ps -ef | grep mysqld没有任何 mysqld 进程。处理方式很简单,确认没有进程后,把旧的 pid 文件删掉再启动:
rm -f /var/run/mysqld/mysqld.pid systemctl start mysql还有一个容易忽略的点:/var/run/mysqld目录必须存在且属主为 mysql。如果这个目录不存在,mysqld 连 pid 文件都创建不出来,启动依然失败。可以直接创建并授权:
mkdir -p /var/run/mysqld chown mysql:mysql /var/run/mysqld在 systemd 服务文件里也可以增加RuntimeDirectory=mysqld让 systemd 自动创建运行时目录,这是更规范的做法。
4.2 socket 文件冲突
另一个隐蔽问题是 socket 文件冲突。Linux 下连接本地 MySQL 默认走 unix socket,如果 socket 文件路径对应的文件已经存在,而且持有它的进程不是 mysqld,启动时就会报Bind on unix socket: Address already in use。
这类情况常见于当你在一台机器上同时运行过两个 MySQL 实例,或者之前启动失败后留下了残留的.sock文件。有些初始化脚本会在启动时自动清理 socket,但如果脚本逻辑有问题,或者你是手动通过mysqld_safe启动的,socket 残留概率会大很多。
排查时执行:
ls -l /var/run/mysqld/mysqld.sock如果文件存在,用ss -lx | grep mysql看看是否有进程监听。如果没有进程监听,说明是残留文件,直接删除即可。如果有进程监听,说明你确实已经有 MySQL 在运行,此时再启动就会冲突,需要停掉旧进程或修改新实例的 socket 路径,而不是粗暴删文件。
从经验来看,socket 冲突导致的status=1很容易和 pid 冲突混在一起,因为日志里可能只出现Address already in use,没有具体说明是 TCP 还是 socket。这时用ss -lntp和ss -lx同时检查 TCP 3306 与 socket 路径,基本能一锤定音。
4.3 InnoDB redo/undo 损坏的应急处理
最让运维头疼的隐性问题,就是 InnoDB 损坏。如果日志里出现InnoDB: Database page corruption或者InnoDB: redo log ... corrupted,常规启动基本无解,因为 InnoDB 在恢复阶段发现数据页或重做日志不一致,会拒绝继续启动以避免扩散损坏。
这种时候,我的第一反应不是直接删日志文件,而是判断损坏级别,然后尝试以强制恢复模式把数据导出来。MySQL 提供了innodb_force_recovery这个参数,从 1 到 6 逐级增加恢复操作的限制,等级越高,InnoDB 就越“忍让”,但可执行的操作也越少。
配置方法是在my.cnf的[mysqld]段加上:
innodb_force_recovery=1然后尝试启动。如果 1 不行,再改成 2、3、4、5。注意,等级达到 4 之后 InnoDB 会在 SELECT 时返回不可靠数据,等级 6 时强制启动几乎只用来做全量导出。启动成功后,不要开业务,直接用mysqldump或客户端把核心表数据导出到本地文件,然后再用正常模式重建实例,把数据导入回去。
关于 redo log,这里有个版本差异需要特别注意:在 MySQL 5.7 及更早版本中,重做日志是ib_logfile0和ib_logfile1,在极端情况下有人会选择删除这两个文件来跳过崩溃恢复,但这一定是在你有完备备份的前提下。从 MySQL 8.0.30 开始,重做日志被集中到#innodb_redo目录,不能再用传统删除文件的方式处理。我个人强烈不建议通过删 redo 日志来“解决”启动失败,因为这种情况下极容易丢失最近提交的事务数据,风险太大。正确做法是先用innodb_force_recovery把数据导出来,再重新初始化数据目录,宁可麻烦一点,也不能把数据弄丢了。
5. 实战排查演示:从错误到恢复的完整过程
5.1 场景一:权限问题导致的启动失败
某次在某测试服务器上,同事反馈 MySQL 启动失败,状态显示status=1/FAILURE。我按照流程,先看服务状态:
systemctl status mysql输出里有:
Process: 12345 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid (code=exited, status=1/FAILURE)随后继续看日志:
journalctl -u mysql --since "10 minutes ago"日志里关键行:
[ERROR] InnoDB: Operating system error number 13 in a file operation. [ERROR] InnoDB: The error means mysqld does not have the access rights to the directory.看到13和access rights,基本确定是权限问题。执行ls -ld /var/lib/mysql,发现属主是root,而不是mysql。应该是之前有人用 root 手动处理过数据目录。修复命令如下:
chown -R mysql:mysql /var/lib/mysql chmod 750 /var/lib/mysql systemctl start mysql重启后,服务正常启动。这个案例虽然基础,但频率极高。我后来养成了一个习惯:任何涉及数据目录的手工操作,结束前都检查一遍属主,省得后续踩坑。
5.2 场景二:磁盘空间耗尽导致的启动失败
另一个场景,某开发环境的 MySQL 突然起不来,日志里只有简单的一句:
[ERROR] InnoDB: Error number 28 means 'No space left on device'.执行df -h,/分区 100%。再执行du -sh /var/lib/mysql/*排序查看,发现 binlog 小时把空间塞满了。当时实例已经无法正常启动,不能通过 SQL 清理,我只能找到自动清理脚本删除了几天前的 binlog 文件。这里注意,直接删 binlog 存在一定风险,如果这些 binlog 是某些场景下用来做时间点恢复的,删了就没了。所以在日常维护中,我强烈建议给 binlog 设置合理保留期,并开启定时删除机制。
清理完成后,df -h确认有空间,再启动 MySQL 就成功了。后续我还给系统加了空间监控,超过 85% 就告警,避免再次被动。
5.3 场景三:配置文件参数不兼容导致的启动失败
还有一次,某同学在安装 MySQL 8.0 时,直接把 5.7 的配置文件搬过来用,启动直接失败。日志里出现:
[ERROR] unknown variable 'query_cache_size=1M' [ERROR] Aborting看到这种unknown variable,说明有参数在当前版本不被识别。解决起来不复杂,在配置文件中把对应参数注释掉,再执行mysqld --validate-config校验,然后启动即可。但这个案例提醒我,升级前最好用官方工具自动检查配置兼容性,避免因为一个旧参数挡住整个启动流程。
5.4 常见问题速查表
| 问题现象 | 快速判断手段 | 直接处理动作 |
|---|---|---|
status=1+ error log 13 | 权限 | 检查属主,chown -R mysql:mysql |
status=1+ error log 28 | 磁盘满 | 清理文件,确认空间 |
status=1+ unknown variable | 配置不合规 | 注释掉报错参数,校验配置 |
status=1+ Address already in use | 端口/socket冲突 | ss查监听,停旧进程 |
status=1+ pid 文件残留 | 残留进程 | 确认无进程,删除 pid/sock |
status=1+ page corruption | 数据损坏 | 备份、innodb_force_recovery导出 |
这张表可以打印出来贴在工位上。遇到status=1,先对号入座,能省下大量盲目试错的时间。
6. 修复后的验证与预防建议
6.1 启动成功后还需要检查什么
很多同学看到一个active (running)就觉得万事大吉,其实启动成功不等于一切正常。我一般会在服务起来后做三件事。
第一件,看一次错误日志。执行tail -n 50 /var/log/mysql/error.log,确认最新日志里没有持续的[ERROR]或异常[Warning],特别是有没有 InnoDB 恢复过程中的警告。
第二件,连接测试。用本地 socket 连接一次:
mysql -uroot -p -e "select version();"如果本地连接没问题,再检查端口监听情况:
ss -lntp | grep 3306第三件,确认关键参数是否按预期生效。比如用SHOW VARIABLES LIKE 'datadir';、SHOW VARIABLES LIKE 'port';,看看和配置文件是否一致。曾经遇到有人改了端口到 3307,结果服务起了一个 3306 的实例,就是因为配置文件包含顺序问题,修改没被读到。
6.2 怎么避免下一次启动失败
与其每次故障后紧急修复,不如在平时把预防做扎实。根据我这几年的经验,以下几个习惯能减少大多数启动问题。
第一,任何配置变更后,先执行mysqld --validate-config再重启。这个命令在 5.7 和 8.0 里都能用,能提前拦截未知参数、明显路径错误等问题。
第二,日志集中管理。统一指定log_error=/var/log/mysql/error.log,并配合日志轮转,避免日志文件无限增长占满磁盘。同时开启监控,当错误日志里出现[ERROR]数量突增或磁盘剩余空间低于阈值时及时告警。
第三,数据目录和运行时目录的属主固定为mysql,不要轻易用 root 操作目录内容。如果必须操作,操作完检查一遍属主。
第四,在 systemd 服务文件里设置合理的资源限制。重点看LimitNOFILE、LimitNPROC,不要让它继承系统的 1024 默认值。修改后执行systemctl daemon-reload再重启服务。
第五,维护一份当前实例的基线信息,包括数据目录、配置路径、socket 位置、pid 位置、已经生效的重要参数。故障发生时,这份清单能让你快速判断哪些信息被改了,哪些不对。
写到这里,我个人的体会是,status=1/FAILURE本身并不可怕,它更像是一个模糊的路标,指着一堆可能的故障方向。真正解决问题的关键,永远是第一时间看日志,然后按逻辑去排除,而不是凭感觉乱改配置。如果你手头正好遇到这个问题,建议从journalctl开始,一行一行往下读,你大概率会在十分钟内找到那个真正让你睡不着的错误。