
最近是不是也被这么一串东西折腾到头大● mysqld.service - MySQL Server Loaded: loaded (/etc/systemd/system/mysqld.service; enabled; vendor preset: disabled) Active: failed (Result: exit-code) since ... Process: 12345 ExecStart/usr/sbin/mysqld (codeexited, status1/FAILURE)codeexited, status1/FAILURE这个报错在Linux上用systemd管理MySQL的人基本都见过。它的迷惑性在于status1只告诉你进程退出码是1但MySQL为什么退出它一个字都不说。我早期刚接触服务器的时候遇到这个报错第一反应是到处问人、反复重启折腾半天发现毫无头绪。后来踩的坑多了才明白这个报错的真正价值不是告诉你哪里坏了而是告诉你去哪里找答案。这篇文章我就围绕这个经典的启动失败场景完整讲清楚我的排查链路从读懂systemd的报错信息到定位真实日志再到按概率从高到低扫一遍常见根因最后用一个我实际处理过的案例串起来。顺便把Windows压缩版和Docker里装MySQL的启动失败也讲了这两个场景的热度一点不比Linux裸装低。1. 报错本身没意义真正的原因都在日志里很多人看到status1/FAILURE就开始慌了其实这个报错只是systemd告诉你你让我启动的程序自己退出了退出码是1。至于为什么退出systemd不知道它也不负责知道。1.1 先看懂 systemd 服务报错三段长什么样一个典型的systemd服务启动失败systemctl status mysqld会输出好几段信息。拆开来看其实就三类Loaded段告诉你这个服务脚本在哪、开机是否自启。比如enabled表示开机自启disabled表示不会。Active段核心信息failed (Result: exit-code)说明服务启动失败后面还有since时间点和process退出码。Process段ExecStart是实际执行的命令codeexited表示主进程退出status1/FAILURE是退出码。如果只是status1问题可能出在配置、权限、资源、依赖等很多方面。但如果你看到的是status0却仍然failed那通常是服务脚本里的ExecStartPost之类的后置检查命令失败这类情况相对少见但逻辑要清楚。真正要盯的是以下几个方向要么直接看MySQL错误日志要么用前台模式启动看终端输出。systemd自己的日志journalctl -u mysqld也会记录一部分信息但它记的往往是systemd视角的内容比如进程退出超时之类不一定包含MySQL内部的具体错误。1.2 日志才是破案的唯一线索MySQL的日志位置因发行版和安装方式差异很大但通常跑不掉这几个地方/var/log/mysql/error.logDebian/Ubuntu系最常见/var/log/mysqld.logCentOS/RHEL系常见或者/var/log/mysql/mysqld.log/var/lib/mysql/*.err部分源码编译或自定义安装会放在datadir里Docker场景下主要是docker logs 容器名后面单独说我遇到过有人翻半天找不到日志最后发现是自己改了my.cnf里的log_error路径但目录没建或者权限不对MySQL想起了也写不进去。所以我的习惯是先看一眼当前生效的配置里log_error指向哪里再去那个路径找。# 查看日志位置 mysqld --verbose --help 2/dev/null | grep -A 1 log-error # 或者直接翻配置 grep -r log_error /etc/my.cnf /etc/mysql/ 2/dev/null日志文件可能很大别一上来就vim整个打开用tail -n 50看尾部最新内容。如果日志里最后几行刚好停在某个错误或者Aborting上那基本就是根因了。1.3 前台启动比翻日志更快的定位手段有时候日志写得比较笼统比如只写了InnoDB: Assertion failure看不出具体原因。这时候最快的办法是不通过systemd直接前台启动MySQL让错误直接打到终端上# 先停掉服务然后前台起 systemctl stop mysqld sudo -u mysql /usr/sbin/mysqld --usermysql --console--console在Windows上是把日志输出到控制台在Linux上其实等价于让日志打到stderr。这样启动时如果报错所有细节都会直接刷在终端里不用再猜日志路径对不对。这个操作建议在测试环境先练一次。生产环境如果要这么干记得先确认没有其他客户端连着否则相当于强制下线。2. 翻日志之前先把这几类高频根因过一遍status1的根因分布很不均匀我处理过的大概有八成落在这几类里。按概率从高到低排基本是目录/文件权限、配置语法或参数错误、磁盘或内存资源不足、端口冲突、崩溃恢复失败以及SELinux/AppArmor拦截。2.1 权限问题datadir 目录不归 mysql 用户管这类问题在刚装好MySQL、或者从压缩包解压后手动初始化时最常出现。MySQL启动时会以mysql用户也可能是mysqld用户取决于你的配置去读datadir、写redo log、建临时文件。如果datadir的属主不是这个用户启动流程会在很靠前的阶段直接失败。# 看datadir位置 grep -r datadir /etc/my.cnf /etc/mysql/ 2/dev/null # 检查属主属组 ls -ld /var/lib/mysql # 修复 chown -R mysql:mysql /var/lib/mysql关键点是-R。我曾经只改了外层目录的属主没递归改子目录结果启动时依然报错。另外如果用了LVM或者挂载了独立磁盘专门放MySQL数据要确认挂载点的属主也是对的因为挂载点本身的属主往往和/var/lib/mysql不一样。2.2 配置写错一个字节的差距就能让实例起不来[mysqld] innodb_buffer_pool_size 8G这条配置本身没错但如果你的机器物理内存只有4GMySQL启动时申请8G的缓冲池会直接失败。更隐蔽的是单位写错innodb_buffer_pool_size 8GB在某些版本会报错正确的写法是8G。还有一类经典配置坑是skip-networking开了之后又配了bind-address或者port被注释导致默认端口变化这些不会直接让进程退出但会让你的客户端连不上容易被误判成启动失败。严格来说它不算status1但在排查时很容易混淆。如果怀疑配置有问题可以用MySQL自带的校验工具先过一遍mysqld --validate-config这个命令会加载配置并检查语法和参数合法性不启动实例。它不能查出所有运行时问题比如磁盘写不进去但语法和参数值的问题基本都能拦住。2.3 磁盘、内存和端口资源类问题最容易被忽略磁盘满是抚养级别的隐蔽问题。MySQL运行时要写binlog、redo log、临时表如果datadir所在分区满了启动时初始化redo log就会失败报错通常长这样[ERROR] InnoDB: The innodb_system data file ibdata1 must be writabledf -h看一下使用率同时用df -i查inode。inode耗尽时磁盘看起来还有空间但文件创建不了这种坑不查inode根本发现不了。内存不足通常表现为Out of memory或者进程被OOM Killer杀掉。排查命令dmesg | grep -i oom journalctl -k | grep -i oom如果看到Out of memory: Kill process字样那就是内存不够用了。解决办法是调小innodb_buffer_pool_size、key_buffer_size、max_connections等参数或者加内存。端口冲突一般报错信息特别明显日志里会直接写Bind on TCP/IP port: Address already in use或者socket文件路径被占用。用ss -lntp | grep 3306看看端口被谁占了或者用lsof /var/run/mysqld/mysqld.sock看socket文件。最常见的冲突来源是装了多个MySQL实例或者有别的服务用了3306。2.4 SELinux 和 AppArmor日志里看不到的隐形拦截这类问题在CentOS和Ubuntu上都有可能遇到但报错五花八门有时候日志里甚至只写了Permission denied但文件权限明明是对的。CentOS系先看SELinuxgetenforce # 如果是 Enforcing看审计日志 ausearch -m avc -ts recent如果日志里出现avc: denied说明是SELinux在拦截。临时关闭验证一下setenforce 0 systemctl start mysqld如果这样能起来说明确实是SELinux的规则问题。这时候别图省事把SELinux直接禁了正确做法是恢复setenforce 1然后针对MySQL放行setsebool -P mysqld_disable_trans 1 # 或者根据具体拦截类型调整文件上下文Ubuntu系对应的是AppArmor同样逻辑看/var/log/syslog里的apparmorDENIED记录。判断出是AppArmor拦截后在/etc/apparmor.d/里找到MySQL对应的profile把你自定义的数据目录路径加进去然后systemctl reload apparmor。3. 一次真实排障从模糊报错到根因的水面下全过程前面讲的是静态知识点但实际排查的时候路径往往是绕弯的。这里分享一个我印象很深的案例完整复现从看到status1到最终定位的全过程过程中我怎么猜、怎么验证、怎么走弯路都写出来。3.1 现象和第一阶段排查陷入了改配置的死胡同那是一个测试环境的MySQL 8.0实例头一天还好好的第二天同事说连不上了。我上服务器执行systemctl status mysqld输出就是经典的codeexited, status1/FAILURE。首反应是看日志tail -n 50 /var/log/mysql/error.log结果日志最后停在这几行[ERROR] [MY-010934] InnoDB: Dictionary statistics table mysql.innodb_table_stats is missing. [ERROR] [MY-012526] InnoDB: Unable to load persistent statistics. [ERROR] [MY-010934] InnoDB: Failed to initialize database. [ERROR] [MY-011013] InnoDB: Aborting because of missing persistent statistics.这类报错看起来像数据字典出问题了我当时第一反应是表损坏或者数据文件被破坏于是在网上找了一圈看到有人说删除mysql.ibd然后重启让他重建。好在操作前多长了个心眼先做了数据文件备份然后才尝试。结果重启之后依然失败报错变成了[ERROR] [MY-012585] InnoDB: Table mysql.innodb_table_stats doesnt exist这条路走了半小时宣告失败。现在回头看我第一步就错了一看到missing statistics就往数据损坏的方向想没意识到这大概率是更底层的原因导致的连带故障。3.2 转折点把日志级别拉到最大以后走弯路之后我冷静下来决定不猜了直接前台起sudo -u mysql /usr/sbin/mysqld --usermysql --console --log-error-verbosity3--log-error-verbosity3是MySQL 8.0的参数把日志级别调到最详细同时前台运行让所有输出直接打到终端。这次刷出来的日志明显信息量不一样除了一堆InnoDB的初始化记录滚动到最后出现了关键的几行[ERROR] [MY-012640] InnoDB: Operating system error number 28 in a file operation. [ERROR] [MY-012646] InnoDB: Error number 28 means No space left on device [ERROR] [MY-012640] InnoDB: Operating system error number 28 in a file operation.错误号28No space left on device。我赶紧跑df -h果然/var/lib/mysql所在分区已经100%了。所以前面那两个missing statistics的报错根本不是根因因为磁盘满了MySQL启动时无法完成崩溃恢复、无法写入临时文件InnoDB在启动阶段尝试加载统计信息但发现文件写不进去才抛出了那些误导性很强的错误。3.3 根因确认和修复以及这类崩溃的后续处理根因找到了就好办。先清理空间# 看看什么文件占了空间 du -sh /var/lib/mysql/* 2/dev/null | sort -hr | head -20发现是一个同事前一天往测试库里灌了大量测试数据binlog也膨胀得厉害。我当时采取的应急措施删掉了一部分已经没用的测试表PURGE BINARY LOGS BEFORE NOW()清掉过期binlog确认有20%以上空闲空间后重启MySQL启动成功后我做的第一件事不是开香槟而是执行ANALYZE TABLE mysql.innodb_table_stats; ANALYZE TABLE mysql.innodb_index_stats;因为之前磁盘满导致统计信息加载失败虽然实例起来了但统计信息可能还是旧的顺手重建一下避免后续优化器走错执行计划。这个案例给我的教训特别深当InnoDB启动报错时先看操作系统级错误再看数据库内部错误。很多数据库坏了的假象底层都是磁盘、内存、权限这些最基础的问题。4. Windows 压缩版和 Docker 里的 MySQL 启动失败也这么查status1/FAILURE虽然多见于Linux systemd环境但评论区经常有人问Windows下zip压缩版和Docker容器里的情况这里一起讲了。它们底层逻辑一样但表象和入口不太一样。4.1 Windows zip 包初始化步骤缺失是头号杀手在Windows上很多人下载了mysql-8.0.46-winx64.zip解压后直接运行mysqld结果窗口一闪而过或者报The designated data directory /var/lib/mysql is unusable之类的错误。这通常是没做初始化。# 以管理员身份打开 CMD进入解压目录 mysqld --initialize-insecure --basedirD:\mysql-8.0.46-winx64 --datadirD:\mysql-8.0.46-winx64\data--initialize-insecure会创建一个不需要密码的root账号仅限本机适合首次安装。如果直接--initialize会生成一个随机临时密码藏在日志里后面要用mysqld --console看日志才能找到。初始化成功后才能启动mysqld --console如果看到ready for connections说明实例起来了。之后用mysql -u root连接再执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;设置密码。注册成Windows服务更省心mysqld --install MySQL80 --defaults-fileD:\mysql-8.0.46-winx64\my.ini net start MySQL80如果net start之后服务启动失败去Windows事件查看器或者D:\mysql-8.0.46-winx64\data下的*.err文件里找线索。还有一点Windows下常见的0xc0000005崩溃码本质上是内存访问违规通常是内存条问题、驱动冲突或者程序自身bug这类和status1不同体系但排查时同样要先看日志。4.2 Docker 容器权限和初始化逻辑是重灾区Docker里跑MySQL启动失败的常见坑有两个数据目录权限不对以及容器初始化逻辑被误解。数据目录权限问题典型场景是这样你把宿主机的/data/mysql挂载进容器/var/lib/mysql启动时容器里的mysqld进程以mysql用户运行但宿主机/data/mysql的属主不是UID 999MySQL官方镜像里mysql用户的UID通常是999于是容器起不来。日志里会看到chown: changing ownership of /var/lib/mysql: Permission denied。解决办法sudo chown -R 999:999 /data/mysql如果是SELinux开启的宿主机还要加:z或:Z标签docker run -d -v /data/mysql:/var/lib/mysql:Z mysql:8.0关于初始化逻辑很多人误以为第一次启动容器时挂载了空目录就能自动初始化。实际上官方镜像确实会在数据目录为空时自动执行初始化脚本。但如果你提前手动往目录里放了文件比如从别处拷贝的备份MySQL会认为这是已有数据目录跳过初始化直接启动。如果这些文件不完整自然就失败了。排查容器启动失败第一入口是docker logs 容器名这个输出等价于MySQL的错误日志前面讲的所有看日志的方法在这里同样适用。4.3 跨场景通用的三个定位动作不管在Linux、Windows还是Docker里启动失败的定位思路都收敛到这三步确认报错的主体到底是谁systemd的status1只是外壳真正报错的是MySQL进程本身要进MySQL的日志体系里找答案。找到日志Linux看error.logWindows看data目录下的*.errDocker直接docker logs。按优先级扫根因权限 → 配置 → 空间/内存/端口 → 崩溃恢复 → 安全模块拦截。这个顺序是我反复踩坑总结出来的照这个顺序排查通常最快。5. 启动失败排查清单和两条保命经验最后把排查流程压缩成一份可以直接照着做的清单再分享两条我个人的保命经验。5.1 从报错到根因的快速决策清单按照下面这个顺序走大部分status1问题都能在十分钟内定位第一步锁定日志# Linux tail -n 100 /var/log/mysql/error.log journalctl -u mysqld -n 100 --no-pager # Windows 在data目录下找最新修改的.err文件用记事本打开看尾部 # Docker docker logs --tail 100 容器名第二步异常优先级判断日志特征指向的根因首选动作Permission denied/Cant create/write to file权限或SELinux/AppArmorls -ld检查属主、getenforce检查SELinuxNo space left on device磁盘满或inode耗尽df -hdf -iAddress already in use端口被占用ss -lntp或netstat -anoOut of memory/Killed内存不足dmesgcrash recovery failed/corruptedredo log或数据页异常先别急着删文件查是否由磁盘/断电引发unknown variablemy.cnf参数写错mysqld --validate-config第三步前台启动验证systemctl stop mysqld sudo -u mysql mysqld --usermysql --console --log-error-verbosity35.2 我最常用的两条预防性操作第一改动任何配置之前先把当前生效的配置完整备份一遍。MySQL加载配置的顺序是/etc/my.cnf、/etc/mysql/目录、~/.my.cnf多个文件叠加生效。你改了A文件但实际生效的是B文件的旧值这种瞎子摸象的情况我见过太多次。备份完了之后用mysqld --print-defaults确认当前实际加载了哪些参数。第二给datadir所在分区建一个磁盘水位监控脚本不用太复杂cron里挂一行就行*/10 * * * * df -h /var/lib/mysql | awk NR2 $50 85 {print 磁盘空间告警: $5} /var/log/disk_warn.log这个脚本的价值在于它能在磁盘满之前把你叫醒而不是等到第二天早上收到一堆数据库挂了的告警再去救火。磁盘满导致的MySQL启动失败修复本身不难难的是你永远不知道它什么时候会发生以及它叠加出来的那些误导性报错会浪费你多少时间。我在实际运维中最大的体会是status1这类报错真不用怕。它就像门锁打不开时门上的那个把手真正的问题是锁芯、门框还是钥匙你得先蹲下来看一眼才知道。日志就是那把能看到锁芯的手电筒前台启动则是直接撬开门。把这两个工具用熟了MySQL启动失败对你来说就只是一个流程问题而不是一个让人熬夜的谜题。