先确认MySQL错误日志是否开启及路径:通过mysqld --verbose --help | grep "log-error"查看默认值,以my.cnf中log-error配置为准,常见路径为/var/log/mysql/error.log或/var/lib/mysql/hostname.err;再检查MySQL进程是否运行、日志路径是否存在且mysql用户有读写权限,特别注意logrotate后是否发送USR1信号重载日志。

MySQL 错误日志路径和开启状态怎么确认
MySQL 默认不一定开启错误日志,更不会自动轮转或告警。先确认它是否在运行、写到哪、有没有权限读取:mysqld --verbose --help | grep "log-error" 能看到编译默认值,但实际以 my.cnf 里 log-error 配置项为准。常见位置是 /var/log/mysql/error.log 或 /var/lib/mysql/hostname.err。如果配置了 log-error 但文件为空或不存在,检查 MySQL 启动时是否有权限写入该路径(比如 SELinux 限制、目录属主不是 mysql 用户)。
用 tail -F + grep 实时捕获关键字的可靠写法
别用 tail -f 直接管道进 grep 然后发邮件——grep 默认带缓冲,可能卡住不输出;也别漏掉 -q(静默)和 --line-buffered(行缓存),否则匹配到了也不触发后续动作:
tail -F /var/log/mysql/error.log | \ grep --line-buffered -q -E "(ERROR|CRITICAL|Aborted connection|Out of memory|InnoDB: Corruption)" && \ echo "$(date): MySQL error detected" | mail -s "ALERT: MySQL error log keyword" admin@example.com
注意点:
-
tail -F比-f更健壮,能应对日志轮转(如 logrotate 重命名旧文件) -
grep -E支持多模式,括号必须用反斜杠转义,或改用单引号避免 shell 解析干扰 - 实际部署时建议把
mail换成curl调用企业微信/钉钉 webhook,避免本地没配 MTA - 如果日志量大,
grep匹配后会退出,导致后续新日志无法捕获 —— 必须用while read循环包裹才可持续
Shell 脚本里如何避免重复告警和漏检
直接每行都发邮件会刷屏,但又不能只靠时间窗口去 dedup(比如 5 分钟内同关键字只报一次),因为关键错误可能间隔几秒连续出现。更稳妥的做法是记录上一次匹配的时间戳和关键字哈希:
- 用
stat -c "%Y" /var/log/mysql/error.log判断日志是否被轮转过,轮转后重置状态文件 - 每次匹配后,把
$(date +%s)和md5sum当前行内容写入临时状态文件,下次启动前比对 - 用
inotifywait -m -e modify /var/log/mysql/error.log替代tail -F是另一种思路,但依赖 inotify-tools,且 MySQL 写日志不是严格逐行 flush,可能丢行 - 务必加
set -o pipefail,否则tail挂了grep还在跑,监控就失效了
为什么不能只依赖 MySQL 自身的 log_error_verbosity
log_error_verbosity = 3(默认值)确实会让更多级别日志进错误日志,但它控制的是“写什么”,不是“怎么告警”。即使设成 1 或 2,只要 ERROR 级别日志还在写,你的脚本就能捕获;反过来,设再高也没用——如果 log-error 根本没配,或者 MySQL 启动失败压根没生成日志文件,脚本连入口都没有。真正要检查的是:ps aux | grep mysqld 确认进程在跑、ls -l $(grep log-error /etc/my.cnf | awk '{print $3}') 确认路径可读、sudo -u mysql test -r /path/to/error.log 确认权限。
最常被忽略的是 logrotate 的 postrotate 脚本没发 HUP 信号给 mysqld,导致 MySQL 继续往已重命名的旧文件写,而你的脚本还在监控新空文件 —— 查 /etc/logrotate.d/mysql 里有没有 kill -USR1 `cat /var/run/mysqld/mysqld.pid` 这类逻辑。


















