MySQL 8一键安装与巡检脚本实战:从部署到健康检查
跑数据库运维没几年但我越来越确认一件事装 MySQL 这件事如果每次都靠手工敲命令总有一天会因为一个参数写错而丢掉一个通宵。尤其到了 MySQL 8初始化方式、认证插件、参数默认值都和 5.7 有差异照着旧文档抄很容易翻车。后来我把自己负责的几十套 MySQL 8 环境全部收编成两套脚本一套是生产可用的 MySQL8 一键安装脚本另一套是一键巡检脚本。安装脚本负责把一台裸机变成标准化实例巡检脚本负责每天凌晨把每套实例的底裤翻出来看一遍。今天这篇就把这两套脚本的设计思路、核心代码、参数计算方式、踩过的坑全部摊开讲给同样被 MySQL 环境交付和日常巡检折腾过的 DBA、运维和全栈同学一点参考。1. 为什么我把安装和巡检都做成了脚本1.1 一次参数写错引发的通宵先说个真实经历。有一回给业务侧新交付一套 MySQL 8.0 实例我自认为熟练手速很快地把解压、初始化、改密码、配参数流程一路点完。结果第二天业务高峰连接数直接被打满数据库疯狂拒绝连接。上去一看max_connections抄的是上一台小规格机器的 200而这台机器内存是 64G业务并发量也是按 64G 规格预估的。更尴尬的是启动参数里我忘了加log_bin导致那段时间主从复制根本搭不起来所有数据只有本机一份谁都不敢碰。凌晨两点在机房改参数、重启、补建复制整个过程让我非常清醒地认识到手工操作带来的不确定性比机器本身的不确定性可怕得多。那次之后我开始把 MySQL 8 的部署流程拆成标准化步骤全部固化成脚本。安装脚本解决的是“每次装出来的实例都一样不会因为手滑而埋雷”巡检脚本解决的是“装完之后的每一天不用等业务报障才知道数据库快不行了”。这两件事本质上是同一件事让数据库环境变得可控、可预期。1.2 这两套脚本到底解决什么问题安装脚本解决的痛点非常具体新环境交付时把下载二进制包、创建系统用户、生成 my.cnf、初始化数据目录、配置 systemd、安全初始化这一长串手工步骤合并成一条命令。多实例部署时不需要把整个安装流程重复操作 N 遍传个不同的端口和目录就能再出一套实例。避免“这台的参数和那台不一样”这种玄学问题所有实例共用一套基线配置。巡检脚本解决的问题更偏日常每天自动检查实例存活、连接数水位、慢查询、主从延迟、锁等待、磁盘空间、binlog 积压这些关键指标。把数据库状态输出成一份带评分、带分级告警的报告而不是靠人肉登录一台台看。给监控平台提供退出码和结构化结果异常时第一时间被感知。这两套脚本不是我拍脑袋写的而是在几十套生产环境上反复磨出来的。如果你的工作里也需要频繁交付 MySQL 8或者你正在管理好几套 MySQL 实例却还没有一个像样的巡检机制下面的内容应该对你有用。2. MySQL8 一键安装脚本从下载到上线的完整链路2.1 脚本的输入参数与使用方式我不喜欢那种写死目录和密码的安装脚本换个机器就得改半天。所以这个安装脚本把所有可变项都做成参数默认值按照最常见的生产场景设置。bash install_mysql8.sh \ --port3306 \ --data-dir/data/mysql/mysql3306/data \ --base-dir/usr/local/mysql \ --version8.0.36 \ --memory32G \ --character-setutf8mb4核心参数如下参数默认值说明--port3306实例监听端口多实例场景改成 3307、3308 即可--data-dir/data/mysql/mysql$PORT/data数据目录建议独立数据盘--base-dir/usr/local/mysql二进制安装目录多实例共用一套--version8.0.36MySQL 小版本号用于拼接下载地址--memory自动计算手动指定时按输入值计算 InnoDB buffer pool--character-setutf8mb4字符集新项目一律 utf8mb4--root-password自动生成不传则随机生成并写入受限文件为什么不用 yum 或 apt 装生产环境我更信任官方提供的 generic Linux 二进制包。用发行版仓库装版本往往滞后且编译参数、目录布局都是发行版定的出问题后你很难对齐官方文档。用官方二进制包解压出来拷到目标机器就能跑离线部署也方便。安装脚本在执行前会做几项前置检查当前系统用户是否为 root、目标端口是否被占用、数据目录是否已存在、磁盘剩余空间是否足够、物理内存是否能撑起指定的innodb_buffer_pool_size。这些检查全部放在下载和初始化之前避免执行到一半才发现前置条件不满足。2.2 核心流程与幂等设计整个安装流程我拆成了 8 个阶段参数解析与前置检查下载并校验 MySQL 二进制包创建 mysql 系统用户和目录结构生成 my.cnf 配置初始化数据目录注册 systemd 服务启动实例并做安全初始化输出实例摘要信息最需要注意的是幂等设计。所谓幂等就是同一台机器上同一个脚本重复执行时不会把已经存在的实例再初始化一遍更不会把数据目录清空重建。我的处理原则是如果$BASE_DIR/bin/mysqld已存在跳过解压步骤。如果$DATA_DIR/mysql目录已存在说明已经初始化过跳过初始化步骤。如果 systemd 服务已存在且处于 active 状态直接提示“实例已在运行”并退出绝不重复启动。核心流程的伪代码结构大概是这样的init_env() { # 创建 mysql 用户、数据目录只有不存在时才创建 if ! id mysql /dev/null; then groupadd mysql useradd -M -s /bin/false mysql fi } prepare_binary() { if [ ! -x ${BASE_DIR}/bin/mysqld ]; then tar -Jxf ${TEMP_DIR}/${TARBALL} -C ${TEMP_DIR}/extract --strip-components1 mv ${TEMP_DIR}/extract/* ${BASE_DIR}/ fi } init_datadir() { if [ ! -d ${DATA_DIR}/mysql ]; then mkdir -p ${DATA_DIR} chown -R mysql:mysql ${DATA_DIR} su -s /bin/bash mysql -c ${BASE_DIR}/bin/mysqld \ --defaults-file${CONF_FILE} \ --initialize-insecure \ --basedir${BASE_DIR} --datadir${DATA_DIR} fi }为什么要用--initialize-insecure而不是默认的随机密码初始化因为这步常常是自动化脚本翻车重灾区。默认初始化方式会把临时 root 密码写进错误日志脚本要再去解析日志里的密码很容易因为日志格式变化或者权限问题读不到。--initialize-insecure生成的是一个空密码 root 账号启动后脚本立刻通过 socket 连接并ALTER USER设置新密码整个流程完全可控。每个关键步骤都通过LOG()函数输出带时间戳的日志。生产环境需要审计脚本日志统一落到/var/log/mysql_deploy.log谁的服务器、什么时候装了什么实例翻日志一目了然。2.3 参数自动计算与 my.cnf 模板既然是“生产可用”参数就绝对不能是默认值裸奔。脚本里做了两件重要的事第一根据物理内存自动计算innodb_buffer_pool_size第二按 MySQL 8 的推荐实践生成一份基线 my.cnf。内存计算逻辑参考下面的公式读取/proc/meminfo里的MemTotal换算成 GB。innodb_buffer_pool_size 物理内存 * 60%。如果计算结果超过 120G封顶 120G避免把内存全吃光。如果--memory参数显式指定则以指定值为准。max_connections我建议单独传参覆盖脚本默认按内存规模给基线值内存小于 32G 给 20032G 到 64G 给 300超过 64G 给 500。这个值不是越大越好连接数越大线程栈、buffer pool 消耗都会上去而且很多业务侧的坑反而是连接池把连接数拉满导致的。my.cnf 模板核心部分长这样[mysqld] server-id 23306 port 3306 user mysql basedir /usr/local/mysql datadir /data/mysql/mysql3306/data socket /data/mysql/mysql3306/mysql.sock pid-file /data/mysql/mysql3306/mysql.pid log-error /data/mysql/mysql3306/error.log character-set-server utf8mb4 collation-server utf8mb4_0900_ai_ci # 复制与事务 server-id 23306 binlog_format ROW gtid_mode ON enforce_gtid_consistency ON log_bin /data/mysql/mysql3306/binlog/mysql-bin binlog_expire_logs_seconds 604800 max_binlog_size 256M innodb_flush_log_at_trx_commit 1 sync_binlog 1 # InnoDB innodb_buffer_pool_size 18432M innodb_buffer_pool_instances 8 innodb_flush_method O_DIRECT innodb_io_capacity 1000 innodb_io_capacity_max 4000 # 慢查询 slow_query_log 1 slow_query_log_file /data/mysql/mysql3306/slow.log long_query_time 1 # 连接 max_connections 300 max_connect_errors 100000几个关键参数的选择逻辑server-id我按端口 20000生成保证同一套集群内不会碰撞。binlog_formatROW是 MySQL 8 主从标配虽然日志量比 STATEMENT 大但数据一致性最好。gtid_modeON配上enforce_gtid_consistencyON后面做从库重搭、主从切换都会方便很多。binlog_expire_logs_seconds按秒设置MySQL 8 已经废弃了expire_logs_days这里用 604800 秒就是保留 7 天。innodb_flush_log_at_trx_commit1和sync_binlog1是双 1 配置每次提交都刷盘性能有一定代价但生产环境的可靠性优先。long_query_time1意味着超过 1 秒的 SQL 都会被记录下来。很多人觉得 1 秒太严格但生产环境慢查询日志宁可多记不可漏记后面可以按实际业务调整。2.4 安全基线密码、权限与 systemd安装脚本最后一部分是安全初始化这也是生产环境最关键的部分。默认安装完 MySQL 会有一个空密码 root、匿名用户、test 库这些必须全部处理掉。我用 socket 连接 root 执行一段 SQLALTER USER rootlocalhost IDENTIFIED BY 生成的随机密码; DELETE FROM mysql.user WHERE User; DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Dbtest OR Dbtest\_%; FLUSH PRIVILEGES;如果指定了业务账号也会在这一步创建CREATE USER app_user% IDENTIFIED BY 独立密码; GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO app_user%;这里特别提醒一句root 账号只允许从 localhost 连接业务账号权限按最小化原则给绝不把整个实例的超级权限交给应用。随机生成的 root 密码会写入/etc/mysql/.mysql-secret文件权限设为 600只有 root 可读。systemd 服务配置也是标准化的一部分。生产环境必须让 MySQL 开机自启、异常退出自动拉起[Unit] DescriptionMySQL 8.0 instance 3306 Afternetwork.target [Service] Typenotify Usermysql Groupmysql ExecStart/usr/local/mysql/bin/mysqld --defaults-file/etc/mysql/mysql3306/my.cnf LimitNOFILE65535 TimeoutStartSec600 Restarton-failure RestartSec5 [Install] WantedBymulti-user.targetLimitNOFILE必须调大MySQL 默认文件描述符限制在生产环境根本不够用。Typenotify是 MySQL 8 官方推荐的 systemd 模式mysqld 会主动通知 systemd “我已经准备好接受连接了”避免服务启动脚本误判启动失败。2.5 跑完一套安装的日志解读脚本正常跑完的输出大概是这个样子[2025-01-12 23:10:01] [INFO] 检查系统环境通过 [2025-01-12 23:10:01] [INFO] 检查端口 3306未被占用 [2025-01-12 23:10:02] [INFO] 物理内存 32G计算 innodb_buffer_pool_size18432M [2025-01-12 23:10:05] [INFO] 下载 MySQL-8.0.36通过 [2025-01-12 23:10:08] [INFO] 校验 SHA256一致 [2025-01-12 23:10:20] [INFO] 解压二进制包完成 [2025-01-12 23:10:21] [INFO] 创建 mysql 用户完成 [2025-01-12 23:10:22] [INFO] 生成 my.cnf完成 [2025-01-12 23:11:01] [INFO] 初始化数据目录完成 [2025-01-12 23:11:02] [INFO] 注册 systemd 服务完成 [2025-01-12 23:11:03] [INFO] 启动实例完成 [2025-01-12 23:11:03] [INFO] 安全初始化完成 [2025-01-12 23:11:04] [INFO] 实例 3306 安装成功看到最后一行“安装成功”不代表万事大吉还要自己连上去确认一下变量和状态。我习惯在安装脚本跑完后补一句连接测试mysql -uroot -p${ROOT_PW} -h127.0.0.1 -P3306 -e SELECT VERSION();能打出版本号这台才算真正交付。3. 一键巡检脚本不能只看进程还活着3.1 巡检项到底选哪些很多运维同学做巡检就是mysqladmin ping一下能通就完事。但数据库这种东西进程活着可比不上故意偷偷折磨人的。经常出现的情况是实例 status 是 UP但线程已经堆积、复制已经断了几小时、磁盘空间只剩 2%。所以巡检脚本必须覆盖到“实例还能不能承担业务”的层面。我把巡检项分成 6 大类分类具体指标数据来源存活与基本状态连接数、uptime、版本、线程运行数SHOW GLOBAL STATUS性能关键指标InnoDB buffer pool 命中率、QPS、脏页比例SHOW GLOBAL STATUS复制健康io 线程、sql 线程、主从延迟、复制错误SHOW REPLICA STATUS事务与锁长事务、行锁等待、当前阻塞information_schema.innodb_trx容量风险磁盘使用率、inode 使用率、binlog 积压df、SHOW BINARY LOGS慢查询与错误慢查询数量、错误日志最近级别、Top 大表SLOW LOG、error log、information_schema.tables巡检项不能贪多重点盯那些真正会让系统出问题的指标。比如Threads_connected高不代表危险Threads_running高才是真正的危险信号Innodb_row_lock_waits是累计值连续两次差值大才说明锁冲突严重。3.2 登录凭据的安全处理巡检脚本需要直连 MySQL 拿状态但不能用 root更不能把 root 密码裸写在脚本里。我的做法是单独创建只读巡检账号CREATE USER mysql_monitorlocalhost IDENTIFIED BY 随机密码; GRANT PROCESS, REPLICATION CLIENT ON *.* TO mysql_monitorlocalhost; GRANT SELECT ON performance_schema.* TO mysql_monitorlocalhost; GRANT SELECT ON sys.* TO mysql_monitorlocalhost;PROCESS权限用来查SHOW PROCESSLIST和大部分状态变量REPLICATION CLIENT用来查复制状态performance_schema和sys的只读权限用来查锁等待和大表。这套权限足够巡检脚本用但做不了任何破坏性操作。MySQL 客户端读取认证信息时可以用--defaults-extra-file指定一个 600 权限的配置文件避免密码出现在命令行进程列表中[client] usermysql_monitor passwordxxx host127.0.0.1 port3306chmod 600 这个文件设置属主为运行巡检脚本的账号其他人都没有读权限。3.3 核心采集逻辑与关键命令巡检脚本用 MySQL 客户端连上实例后会先取一次全量状态变量存到临时文件后续指标从临时文件里解析减少重复连接数据库的开销mysql --defaults-file${CNF_FILE} -N -e SHOW GLOBAL STATUS; ${TMP_STATUS}然后从状态文件里取出关键值threads_connected$(grep -w Threads_connected ${TMP_STATUS} | awk {print $2}) threads_running$(grep -w Threads_running ${TMP_STATUS} | awk {print $2}) max_connections$(mysql --defaults-file${CNF_FILE} -N -e SHOW VARIABLES LIKE max_connections; | awk {print $2})复制状态这块需要特别注意 MySQL 版本差异。8.0.22 之前是SHOW SLAVE STATUS之后改成了SHOW REPLICA STATUS。脚本里做兼容先试新命令失败再回退旧命令set e REPLICA_STATUS$(mysql --defaults-file${CNF_FILE} -N -e SHOW REPLICA STATUS\G 2/dev/null) if [ -z ${REPLICA_STATUS} ]; then REPLICA_STATUS$(mysql --defaults-file${CNF_FILE} -N -e SHOW SLAVE STATUS\G 2/dev/null) fi set -e拿到输出后解析关键字段IO_RUNNING$(echo ${REPLICA_STATUS} | awk -F: /^(Replica_IO_Running|Slave_IO_Running):/{print $2} | head -1) SQL_RUNNING$(echo ${REPLICA_STATUS} | awk -F: /^(Replica_SQL_Running|Slave_SQL_Running):/{print $2} | head -1) SECONDS_BEHIND$(echo ${REPLICA_STATUS} | awk -F: /^(Seconds_Behind_Source|Seconds_Behind_Master):/{print $2} | head -1)长事务检测用information_schema.innodb_trx直接扫超过 30 秒的事务都值得关注SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS running_seconds FROM information_schema.innodb_trx WHERE trx_started NOW() - INTERVAL 30 SECOND ORDER BY running_seconds DESC LIMIT 20;锁等待我习惯看performance_schema.data_lock_waitsMySQL 8 里这个视图比老版本的INNODB_LOCK_WAITS更直接SELECT COUNT(*) AS lock_wait_count FROM performance_schema.data_lock_waits;大表清单也放进巡检里方便容量规划SELECT table_schema, table_name, ROUND((data_length index_length) / 1024 / 1024 / 1024, 2) AS size_gb, table_rows FROM information_schema.tables WHERE table_schema NOT IN (mysql, sys, performance_schema, information_schema) ORDER BY (data_length index_length) DESC LIMIT 10;3.4 报告输出、评分与监控集成巡检结果不能是一堆散落的数字要能一眼看出问题。脚本会把采集到的所有指标整理成一份报告并对每一项按危险程度打分。基本分级规则是不满足阈值但是有风险记WARN扣 5 分。已经影响可用或者马上要出故障记CRIT扣 15 分。正常项记INFO不扣分。总分 100 分90 分以上算健康70 到 90 算需要关注70 以下就要拉响警报了。部分阈值参考指标WARN 阈值CRIT 阈值连接数使用率大于 80%大于 95%磁盘使用率大于 70%大于 85%主从延迟大于 30 秒大于 300 秒InnoDB buffer pool 命中率小于 99%小于 95%长事务1 个以上超过 30 秒持续超过 10 分钟输出报告长这样 MySQL 巡检报告 实例: 192.168.1.10:3306 巡检时间: 2025-01-13 02:00:00 总体评分: 86 / 100状态需要关注 [WARN] 连接数使用率 93.5% (281/300) [INFO] InnoDB buffer pool 命中率 99.98% [INFO] 复制状态正常延迟 0 秒 [CRIT] /data/mysql 磁盘使用率 87.2% [WARN] 存在 3 个超过 30 秒的长事务 [INFO] 慢查询数量较基线增加 18 次 脚本退出码也按级别区分0 表示正常1 表示有 WARN2 表示有 CRIT。这样接入监控平台时不用解析文本直接看退出码即可。我这边每天晚上 3 点跑完巡检如果退出码非 0监控平台就会拉起一条告警值班同学直接看报告定位问题。3.5 定时任务与告警迭代定时任务我放在 crontab 里错开业务高峰和管理员休息时间2 3 * * * /usr/local/bin/mysql_check.sh --port3306 --report-dir/var/log/mysql_reports --json /var/log/mysql_check_cron.log 21巡检脚本同时支持多实例。生产环境往往是多端口多实例可以为每个实例配一行 cron也可以在脚本外做一个实例列表循环。我更喜欢后者实例清单维护在一个/etc/mysql/instances.txt文件里巡检脚本逐行读取并巡检新增实例时只改清单不用改 cron。关于告警有一条血泪经验新脚本上线后的第一周千万不要直接开邮件和短信告警。先把输出报告落盘每天上班看一眼阈值有没有误报。等跑满一两周、阈值都验证过再接入告警渠道否则你会被各种假告警骚扰到把告警当作噪音关掉。告警这种机制宁可刚开始少一点也不要让团队失去对它的信任。4. 真实环境里的使用记录4.1 一次安装日志实录我在一套生产用的订单库环境上重新跑了一次安装脚本完整走一遍流程。服务器配置是 32G 内存、8 核 CPU系统盘和数据盘分离数据盘挂在/data。执行命令bash install_mysql8.sh --port3306 --data-dir/data/mysql/mysql3306/data --memory32G脚本先检查了端口、目录和磁盘空间然后开始下载官方二进制包。这里要提醒一下下载阶段非常依赖网络如果机器不能直接访问官方 CDN就得在脚本里预留一个镜像下载地址的变量或者干脆预先把 tar 包放在本地目录。生产环境最好一次性先把安装包下载好放到内网软件仓库后续交付都走内网。初始化阶段值得关注的是数据目录权限。脚本里先mkdir -p并chown -R mysql:mysql再切换到 mysql 用户执行初始化很多手动初始化失败的案例都是因为忘了这一步mysqld 报错Permission denied错误日志里根本看不到实际情况。跑完后我特意验证了 root 密码文件/etc/mysql/.mysql-secret权限确实是 600只有 root 能读。然后我在业务侧建了一个只读账号和一个普通业务账号分别录入权限最小化配置确认应用可以通过 TCP 连接。整个安装到交付脚本耗时不到 5 分钟这还没算等待下载的时间。如果是手工装光敲命令和等初始化就够喝一壶的。4.2 巡检发现了什么问题安装完成之后我让巡检脚本每天凌晨跑第三天就开始发现问题。第一眼看到的是磁盘使用率 WARN。数据目录所在分区用到了 78%再细看巡检报告里的 binlog 信息发现高峰期业务写入量很大7 天的 binlog 积压下来占了将近 200G。原参数binlog_expire_logs_seconds604800保留 7 天并没有错但在这个业务模型下显然没有必要保留这么久和业务确认恢复窗口后我把保留时间调成了 3 天也就是172800秒磁盘使用率立刻降回安全水位。第二个问题是长事务。巡检脚本连续两天都报有超过 30 秒的事务进去查information_schema.innodb_trx发现是一个报表任务发起的事务因为一条关联查询没有走索引全表扫了 200 多万行事务迟迟不提交。虽然还没造成锁阻塞但已经让undo膨胀了不少。这个问题的价值在于如果没有巡检可能要等到它跟后续业务事务形成锁等待之后才会被发现。第三个问题是在慢查询 Top 榜单里看到的某张订单流水表的COUNT(*)查询稳定排在慢日志前列。查了执行计划发现这张表有 2.3 亿行统计信息严重过时导致优化器选择了错误索引。虽然跟巡检脚本本身没有直接关系但它逼着我在巡检报告里增加了一项“Top 大表”信息每次巡检都能盯着这些大表的容量变化。4.3 针对巡检结果做的参数调整巡检不是只看问题还要驱动调优。针对那台订单库我根据巡检报告做了三处调整第一innodb_buffer_pool_size从默认的 18G 调到了 40G。判断依据是巡检报告里 InnoDB buffer pool 命中率只有 97.8%明显低于我常用的 99% 警戒线说明热数据已经放不下了。机器有 128G 物理内存调大 buffer pool 到 40G 不影响其他进程命中率很快就回升到 99.9% 以上。第二把long_query_time从 1 秒适当放宽到 1.5 秒。为什么放宽因为巡检报告发现某些业务场景下 1 秒以上的查询虽然多但执行计划正常、返回行数合理属于业务本身复杂。如果不放宽慢查询日志会越来越大反而掩盖真实需要优化的慢查询。这里要强调的是放宽阈值必须配合巡检的“慢查询数量偏离基线”提醒机制不然退回盲区。第三调了连接数。之前max_connections是 300巡检发现峰值连接使用率稳定在 60% 左右但连接池最大连接数设置存在放大效应一有升级发布就会短时间冲到 85%。我不建议直接拉大max_connections而是跟应用团队沟通把连接池上限压下来。这种做法比单纯调大数据库参数健康得多。参数调整要记住一个原则临时调参和持久化必须同时做。比如动态改SET GLOBAL long_query_time1.5之后必须同步更新 my.cnf 里的对应值否则 MySQL 一重启又回到旧参数等于白调。脚本环境里我习惯于把特殊实例的覆盖参数放在独立的conf.d片段里主模板保持统一升级基线时不至于把特化配置冲掉。5. 常见问题与避坑经验5.1 问题速查表这两套脚本用下来我整理了一张高频问题速查表基本覆盖了我自己踩过或者同事踩过的大部分坑现象常见原因处理方法mysqld: error while loading shared libraries: libaio.so.1系统缺 libaio 和 numactl 库Debian/Ubuntu 装libaio1 numactlCentOS/RHEL 装libaio numactl-libs初始化报Permission denied数据目录属主不是 mysqlchown -R mysql:mysql $DATA_DIR安装后本地 root 登录不了root 默认 localhost且密码文件没读到确认/etc/mysql/.mysql-secret内容和权限mysqld 启动后被 systemd 反复拉起数据目录不完整或 my.cnf 路径错看error.log确认datadir里有mysql目录远程连接报Authentication plugin caching_sha2_password客户端/驱动版本太老升级驱动到支持 MySQL 8临时方案是建账号时指定mysql_native_password但只建议过渡用巡检脚本连不上实例巡检账号权限不足或 .my.cnf 权限过大核对GRANT确认配置文件 chmod 600复制状态一直显示 No主从复制账号没有起检查主库 binlog、从库START REPLICA再看Last_IO_Errno磁盘使用率报警不停binlog 保留时间太长或 Xtrabackup 备份文件堆积确认binlog_expire_logs_seconds清理过期备份慢查询日志里看不到预期 SQLlong_query_time只对普通语句生效管理语句默认不记需要时开log_slow_admin_statementsON单机多实例时端口冲突复制了旧 my.cnf 没改端口/socket每个实例独立配置目录确认 port、socket、pid 不共用5.2 脚本迭代中的几条心得这套脚本不是一次写成的是不断加需求、踩坑、改出来的。有几条心得写在这里希望对正在做类似事情的人有帮助。第一安装脚本写完之后一定要在全新干净的系统上验证最好用虚拟机或者容器起一个最小化系统跑一遍。最开始我在一个有历史 MySQL 环境的机器上测很多问题被掩盖了等换到干净机器立刻暴露了缺依赖、目录冲突的问题。干净环境能跑通才能叫“一键”。第二巡检脚本的阈值不能一次定死要观察至少两个业务周期。不同业务的连接数、慢查询、主从延迟表现差异很大。比如一个 3D 渲染平台凌晨任务一大堆连接数常常很高但这是正常规律另一个偏 OLTP 的订单系统凌晨要是连接数高那就是异常。所以初版巡检脚本我会把阈值调得宽松一点先收集一段时间的基线数据再逐步收紧。第三任何脚本改动都要走变更流程。这个听起来很重但在生产环境里真的很重要。安装脚本一旦更新可能影响之后所有新交付实例的初始化行为巡检脚本一旦更新可能改变告警判定逻辑。没有版本管理和变更记录出了问题你都不知道是哪一版脚本干的。第四巡检报告一定要留历史。我现在的脚本会把每次巡检的 JSON 报告按日期归档然后每天跟昨天的报告做 diff。很多问题不是单看某一天的某一个指标就能发现的比如Threads_running连续一周缓慢上升这种趋势比单日告警更有预兆性。我看过太多“昨天还好好的今天突然挂了”的故障其实早就有迹可循只是没人把前后的巡检数据拉出来对比。最后分享一个小技巧也是我现在日常最依赖的一个动作巡检脚本跑完后把评分和各项关键指标写到一个固定的.txt文件里每天早上用diff对比昨天的文件。新增出来的 WARN 往往比仓储里的汇总告警更容易激发人去查一次。数据库巡检这件事怕的不是告警太多而是告警太多之后没人看让每一次变化都变得显眼大家才会真的把巡检当回事。