直接敲 systemctl restart mysqld.service,屏幕上弹出一行:
code复制Job for mysqld.service failed because the control process exited with error code.
See "systemctl status mysqld.service" and "journalctl -xeu mysqld.service" for details.
很多刚接触 systemd 的朋友第一反应是“那再 restart 一次”,结果第二次还是原样。这不是手气问题,而是 mysqld 进程启动时确实失败了,systemd 只是把结果转告给你。这篇文章我直接把这套排错链路讲透:报错什么意思、日志去哪看、最常见的几个失败原因、完整的复盘案例,顺便把 systemctl list-units 这类高频命令一并说清楚。内容基于我在生产环境处理这类故障的常见实践,命令和思路都可以直接抄。
1. 先看懂报错:systemd 在向你传达什么
1.1 报错里的每个字段分别代表什么
把上面那行报错拆开看:
Job for mysqld.service failed:systemd 这次想启动mysqld.service这个单元,任务失败了。because the control process exited with error code:负责启动服务的“控制进程”退出时返回了非零状态码。换句话说,/usr/sbin/mysqld这个程序根本没能在前台正常运行起来,自己退出了。See "systemctl status mysqld.service" and "journalctl -xeu mysqld.service" for details:这是提示你下一步去哪看详细原因。
这行文字本身不包含“为什么 MySQL 起不来”的答案,它只告诉你“有人启动失败了”。真正的失败原因藏在两个地方:mysqld 的错误日志,以及 systemd 的日志。如果你盯着这行报错发呆,那就是走错了路。
1.2 控制进程、主进程、退出码的关系
systemd 和服务进程之间的关系,可以简单理解成“宿管叫早”。宿管(systemd)负责敲门、确认屋里的人醒没醒,但它不会进屋替你把灯打开。如果早上没见到人,宿管只知道“这屋没起来”,至于是因为闹钟没响、昨晚熬夜还是门锁坏了,宿管不关心,也不会在门口贴详细原因。
具体到 mysqld.service 上:
- systemd 读取
/usr/lib/systemd/system/mysqld.service里的ExecStart,执行真正启动 MySQL 的命令。 - 这个被拉起的进程如果正常工作,会一直待在后台,也就是保持“运行中”状态。
- 如果进程启动到一半发现配置不对、目录写不进去、磁盘满了、端口被占,就会自己退出,并且返回一个非零的 exit code。
sysd 看到的只是“退出码 1 或者退出码 0x7f”这一层。MySQL 内部到底是因为什么返回的退出码,它不会去解析。所以你现在最需要做的,不是反复 restart,而是去看日志。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 别只盯着报错行,把状态和日志完整捞出来
2.1 systemctl status mysqld.service 输出怎么读
第一步永远是执行下面这条命令:
bash复制systemctl status mysqld.service
输出大致是这样:
code复制● mysqld.service - MySQL Server
Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Thu 2024-03-14 10:23:01 CST; 2min 11s ago
Process: 3957 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid (code=exited, status=1/FAILURE)
Main PID: 3957 (code=exited, status=1/FAILURE)
重点看这几行:
Process:后面的ExecStart:这里会显示 systemd 具体执行了哪条命令。很多发行版的 mysqld 启动命令里带--daemonize,一旦 MySQL 守护化,systemd 还需要靠PIDFile=配置去追踪真正的进程。status=1/FAILURE:进程返回了状态码 1,表示非正常退出。Active: failed:服务目前处于失败状态,不会自动恢复。
继续追日志的时候,用这条命令看最近 50 行:
bash复制journalctl -u mysqld.service -n 50 --no-pager
想看得更全,加上 -x 关联 systemd 的提示信息,加 -e 直接跳到日志末尾:
bash复制journalctl -xeu mysqld.service
-x 会把日志里能对应上的说明文字一起输出,对新手非常友好;-u 是指定单元名;-e 是跳到尾部实时看;-n 50 表示只看最近 50 行。排查时我建议先用 -n 50 --no-pager,确认末尾几行的 ERROR 字段,再往前翻。
2.2 别忘了 mysqld 自己的错误日志
journald 是 systemd 的日志系统,但 MySQL 通常同时配置了自己的错误日志。很多时候 journald 里只记了一句“mysqld 退出了”,真正详细的堆栈反而在 MySQL 的错误日志里。
日志位置因发行版而异,最常见是两个路径:
- RHEL / CentOS 系列:
/var/log/mysqld.log - Debian / Ubuntu 系列:
/var/log/mysql/error.log - 也有的配置写在
my.cnf里,通过log-error=/var/log/mysql/error.log指定
查看方式:
bash复制tail -n 50 /var/log/mysqld.log
如果错误日志里能看到具体报错,定位就会快很多。比如下面这类:
code复制[ERROR] [MY-010262] [Server] Can't start server: Bind on TCP/IP port: Address already in use
[ERROR] [MY-010931] [Server] Can't start server: Check TCP/IP settings: Address already in use
[ERROR] [MY-012681] [InnoDB] Unable to lock ./ibdata1 error: 11
看到“Address already in use”,基本就是端口被占;看到“Unable to lock ./ibdata1”,大概率是文件被占用,或者权限不对。日志里每一行错误码和上下文,都比 systemd 那行通用提示有价值得多。
2.3 看日志时最容易忽略的一个坑
还有一种常见情况:journald 里没输出,MySQL 错误日志也没新增内容,但服务就是起不来。这时候你先确认一下系统时间。虚拟机和云主机经常出现时间漂移,日志里记录的时间和你“感觉上的现在”差了好几分钟,就会让你误判“刚才没写日志”。
另外,如果磁盘已经满了,日志也无法写入,错误日志文件自然停留在旧内容。所以排查顺序应该是:先看磁盘,再看日志。磁盘塞满导致 MySQL 起不来,我后面会专门展开。
3. 逐项排查:mysqld 启动失败的六大高频原因
3.1 目录权限与属主不对
MySQL 启动时第一件大事就是打开数据目录 /var/lib/mysql,在里面找系统库、redo log、undo log。如果运行 mysqld 的系统用户对目录没有读写权限,进程基本是秒退。
检查命令:
bash复制ls -ld /var/lib/mysql
ls -l /var/lib/mysql | head
正常情况下,数据目录的属主应该是 mysql:mysql,权限类似 drwxr-xr-x。如果之前用 root 手动拷贝过数据文件,或者执行过不规范的 chown -R root:root /var/lib/mysql,启动时就会直接被拒绝。
修复命令:
bash复制chown -R mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
注意,/var/lib/mysql 权限也不能随手给 777。MySQL 对数据目录的安全模式有检查,目录如果可以被任何用户写,部分版本反而会拒绝启动。目录权限定到 750 或 755,属主是 mysql,足够了。
3.2 磁盘空间不够或者 inode 耗尽
这个原因非常隐蔽,错误日志往往只提示写文件失败,甚至直接没有日志。mysqld 启动时需要在数据目录创建临时文件、redo log,还要向错误日志写入启动信息,空间不够时进程活不过 1 秒。
排查命令:
bash复制df -h /var/lib/mysql
df -i /var/lib/mysql
df -h 看的是磁盘剩余空间,df -i 看的是 inode 剩余数量。inode 耗尽时,磁盘明明还有几个 G,但创建不了任何新文件。小文件过多的目录最容易踩这个坑,常见于 /tmp 和定时任务产生大量缓存文件的场景。
发现空间满了,先清出空间再重启。如果连 /var/log 都满了,错误日志也写不进去,会形成“越查越看不明白”的死局。清理时别直接删 MySQL 数据文件,优先处理日志文件、临时文件和回收站目录。
3.3 端口占用、残留 pid 文件和 socket 文件
MySQL 默认监听 3306 端口,如果已经有进程占用,或者上次非正常关机留下了不干净的 pid 文件,systemd 启动时同样会报同样的失败信息。
排查占用:
bash复制ss -lntp | grep 3306
ps -ef | grep mysqld
如果发现真正的 mysqld 进程还活着,说明你根本没有把旧服务停掉,或者 systemd 里还残留一个僵死状态。这时候用 kill 结束旧进程要慎重,优先尝试正常停止服务:
bash复制systemctl stop mysqld.service
如果确认没有存活进程,只剩残留文件,可以检查以下位置:
/var/lib/mysql/*.pid/var/lib/mysql/*.sock/var/run/mysqld/mysqld.pid/var/run/mysqld/mysqld.sock
这些文件在进程不存在时属于无用残留,删除后重新创建目录并设置正确的属主:
bash复制mkdir -p /var/run/mysqld
chown mysql:mysql /var/run/mysqld
我处理过最典型的一次:机房意外断电后,系统重启时 /var/run/mysqld 目录被清空,但旧 pid 路径下没有生成新目录,mysqld 启动时写不了 pid 文件,直接 failed。重建目录、授权,一条命令解决。
3.4 配置文件写错,启动参数不合法
MySQL 配置文件通常位于 /etc/my.cnf 或 /etc/my.cnf.d/ 目录下。每加一行配置,都可能导致启动失败,而且错误信息可能相当抽象。
新版本的 MySQL 支持先验证配置再启动:
bash复制mysqld --validate-config
如果配置有问题,它会直接打印出具体错误,比如未知变量、参数值越界。一个很常见的坑是把参数名写错,比如把 innodb_buffer_pool_size 写成 innodb_buffer_pool,MySQL 8.0 版本会直接拒绝启动,而不是忽略。
想看最终生效的配置项,可以用:
bash复制mysqld --verbose --help 2>/dev/null | grep -E "datadir|socket|port|log-error"
注意 /etc/my.cnf 里的配置是分组的,写在 [mysqld] 段下才会被服务端读取。如果你把配置写进了 [mysql] 或者 [client],启动时不会报错,但也不会生效,容易造成“我以为改了配置,但实际没生效”的假象。
3.5 SELinux 与 AppArmor 的强制访问控制
在 RHEL、CentOS、Fedora 这类系统上,SELinux 默认开启。MySQL 数据目录如果是从其他机器拷贝过来的,文件的安全上下文可能不对,mysqld 访问时会触发 Permission denied,而普通 ls -l 看到权限却完全正常。
检查 SELinux 状态:
bash复制getenforce
检查数据目录的安全上下文:
bash复制ls -Z /var/lib/mysql
正常情况应该包含 mysqld_db_t 这样的上下文。如果不对,执行:
bash复制restorecon -Rv /var/lib/mysql
想让整个目录恢复默认规则,也可以对相关文件统一执行。至于临时关闭 SELinux 来验证问题,setenforce 0 只适合在测试环境快速判断原因,生产环境我不建议用这种方式“解决”问题,关掉安全模块等于把防护层拆了,正确的做法是按规则调整上下文。
Ubuntu 和 Debian 上则是 AppArmor 在起作用。如果 mysqld 的日志里出现 apparmor="DENIED" 字样,说明是 AppArmor 拦截了对某个目录的访问。可以用 aa-status 查看已加载的配置,应用目录变化后需要调整 /etc/apparmor.d/ 下对应 MySQL 的规则,而不是直接禁用 AppArmor。
3.6 数据文件本身损坏或未初始化
上面几项都查完没问题,那问题就出在数据这一层。错误日志里如果出现:
code复制[ERROR] [MY-011972] [InnoDB] Your database may be corrupt
[ERROR] [MY-010951] [Server] Failed to initialize DD Storage Engine
说明 InnoDB 在启动阶段读取数据文件时失败。可能是断电、磁盘坏道、误删文件导致的损坏,也可能是因为误删了 ibdata1、ib_logfile* 等文件。
如果数据目录里的系统表消失、或整个目录空掉了,错误日志会提示缺少系统库表。这时候需要判断:是有备份可以恢复,还是接受数据丢失、重新初始化一个新实例。
初始化新数据目录的命令在 MySQL 5.7 和 8.0 中大致是:
bash复制mysqld --initialize --user=mysql
注意,--initialize 会创建一个全新的数据目录,原来的数据如果没备份,等同于全部清空。执行前一定确认当前目录是否真的没有可用数据,或者已经完成备份。初始化完成后,临时 root 密码会写到错误日志里,第一次登录后必须马上改密码。
提示:出现数据文件相关错误时,我的建议永远是先停服,不要反复重启。反复启动不仅不会修复损坏,还可能让 InnoDB 重放日志时进一步扩大损坏面积。先把数据目录完整拷贝一份,再尝试恢复,是更稳的思路。
4. 三个真实排错复盘,跟着流程走一遍
4.1 场景一:服务器重启后 MySQL 起不来
一台 CentOS 7 上的 MySQL 8.0,机房重启后 systemctl start mysqld 报了开头的错误。控制台输出:
code复制Process: 2146 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid (code=exited, status=1/FAILURE)
我先看了 /var/log/mysqld.log,最后几行是:
code复制[ERROR] [MY-010457] [Server] Can't open file '/var/lib/mysql/ibdata1' for writing. OS error: 13
错误码 13 就是 permission denied。接着执行:
bash复制ls -ld /var/lib/mysql
结果 /var/lib/mysql 的属主变成了 root:root。原因是这台机器的数据盘做了开机挂载,挂载时把整个目录的属主覆盖了。解决方法是重新归属并设定目录权限:
bash复制chown -R mysql:mysql /var/lib/mysql
chmod 750 /var/lib/mysql
随后 systemctl start mysqld,服务正常起来。整个过程不到 5 分钟。这个案例的启示是:看到错误日志里的 OS error 数字,先去查它对应的含义,而不是上网搜那行 systemd 报错。
4.2 场景二:mysqld.sock 文件残留引发的假死状态
一台 Ubuntu 20.04 上的 MySQL 5.7,某天凌晨执行过 kill -9 杀掉了一个卡住的 mysqld 进程。白天再启动时,同样报 control process exited with error code。
查看 journald:
bash复制journalctl -u mysql.service -n 30 --no-pager
发现日志里反复提示:
code复制[ERROR] Can't create/write to file '/var/run/mysqld/mysqld.sock' (Errcode: 13)
mysqld.sock 是 Unix socket 文件,但目录可能不存在了。因为 kill -9 是强杀,进程来不及清理就没了,有些临时目录在系统启动时被清空后并不会自动重建。
修复:
bash复制mkdir -p /var/run/mysqld
chown mysql:mysql /var/run/mysqld
systemctl start mysql
kill -9 是数据库的大忌。如果担心服务卡死,先执行 systemctl stop,再用 kill 命令温和结束,没有特殊情况不要直接上 -9。
4.3 场景三:磁盘满导致 redo log 写不进去
某台阿里云主机报 mysqld.service 启动失败,控制台提示和上面一样。查 /var/log/mysql/error.log,只发现最后一行:
code复制[ERROR] [MY-012640] [InnoDB] Operating system error number 28 in a file operation.
错误码 28 代表设备上没有剩余空间。执行:
bash复制df -h
发现根分区 / 的使用率已经 100%。进一步排查,是 /var/log/mysql 下的 binlog 和慢查询日志把磁盘吃光了。当时先清掉了一部分超过保留期的 binlog,又对日志目录做了压缩归档,最后:
bash复制systemctl start mysqld
服务恢复。随后我加上日志清理策略和磁盘空间告警,避免再次踩坑。如果你碰到「错误日志里什么 ERROR 都没有,甚至日志完全没有更新」的情况,第一反应就应该是看磁盘。
5. 顺手搞懂 systemctl list-units 及相关服务管理命令
5.1 systemctl list-units 到底列的是什么
“systemctl 怎么查看所有的 service”是很多人搜过的热词。systemctl list-units 确实能列服务,但它的确切含义是:列出当前 systemd 已经加载到内存中的单元(unit),并且只显示处于 active 状态或正在处理的单元。
直接执行:
bash复制systemctl list-units
你会发现列出的内容里面只有正在运行的、已经退出的(如果有 --all)单元,像 mysqld.service 挂了但还保留在内存里一样会被看到;但如果你有一个服务从未被启动,它不会出现在这里。
所以要看所有加载到内存的服务类单元,包括 failed 和 inactive 状态,用:
bash复制systemctl list-units --type=service --all
要看磁盘上所有可用的 unit 文件,以及它们在开机时是否会启动,用:
bash复制systemctl list-unit-files
按服务类型过滤:
bash复制systemctl list-unit-files --type=service
想看哪些服务设成了开机启动:
bash复制systemctl list-unit-files --type=service | grep enabled
日常排查中,我更常用的是:
systemctl is-active mysqld:只看是否处于 active。systemctl is-enabled mysqld:只看是否开机启动。systemctl status mysqld:包含状态、进程、最近日志的综合信息。
区分这三者关系:
| 命令 | 作用 | 常见返回值 |
|---|---|---|
systemctl status mysqld |
查看服务完整状态、进程、日志 | 一长段信息 |
systemctl is-active mysqld |
只判断服务是否在运行 | active / inactive / failed |
systemctl is-enabled mysqld |
只判断开机是否自启 | enabled / disabled / static |
systemctl list-units --type=service --all |
列出已加载的服务 | 服务列表 |
systemctl list-unit-files --type=service |
列出所有可用服务文件 | 服务列表 |
5.2 排查 mysqld.service 启动失败的推荐顺序
这里给一份我日常使用的排错速查,按这个顺序走,大多数问题都绕不开圈子:
- 执行
systemctl status mysqld.service,记录ExecStart和status。 - 执行
journalctl -u mysqld.service -n 50 --no-pager,看 systemd 视角。 - 打开 MySQL 错误日志文件,看末尾 50 行。
- 执行
df -h和df -i,排除磁盘与 inode 耗尽。 - 执行
ps -ef | grep mysqld和ss -lntp | grep 3306,排除进程残留和端口占用。 - 执行
ls -ld /var/lib/mysql,检查权限和属主。 - 根据错误日志关键字定向处理:
Address already in use、Permission denied、OS error number 28、Unable to lock、Can't open file。
这套流程基本覆盖了 90% 的“控制进程退出”案例。如果日志里出现 Killed 字样,多数是内存不足触发了 OOM,这时候看 journalctl -k 或 dmesg 里有没有 oom-killer 的记录。
5.3 一些值得长期坚持的排查习惯
写到最后,我把这几年处理 mysqld 启动失败问题的经验总结成几条。排查时尽量少用 restart,多用 start。服务已经处于 failed 状态,用 start 就能看到最新一次启动日志;而 restart 偶尔会掩盖一些启动顺序带来的问题,还会让你多刷一轮日志。
启动脚本或 systemd 服务文件的改动,要配合 daemon-reload 才会生效:
bash复制systemctl daemon-reload
改了 /etc/my.cnf 后,先跑一遍 mysqld --validate-config,能省掉很多次无效重启。日志文件建议用 logrotate 按时滚动,不要让它无限制增长。最后,给数据目录和错误日志单独做一次磁盘空间监控,告警阈值定在 80% 就比较合适,别等到 100% 才收到通知,到那一步 MySQL 多半已经起不来了。
