☰
MySQL数据库系统维护实战:服务生命周期与健康闭环
2026/9/26 13:53:29 网站建设 项目流程

简介:本资源是国家开放大学MySQL基础课程配套实验训练材料,面向数据库初学者与远程教育学员,聚焦数据库系统日常运维核心技能。内容围绕汽车用品网上商城(Shopping数据库)展开,系统覆盖用户与权限管理(创建Teacher/Student账户并差异化授权)、mysqldump备份与恢复、二进制日志启用、多方式数据导出(SELECT INTO/命令行/Workbench+GBK字符集防乱码)与导入(LOAD DATA/mysqlimport/Workbench),以及基础优化与修复要点。文档为单个3.59MB的Word文件(.docx),结构清晰,含8个编号实验、详细操作步骤、执行结果分析及典型问题提示,便于边学边练、理解权限控制逻辑与灾备流程。目前已有1980人学习下载,适合课程作业完成、实操复盘及数据库管理员入门训练。

1. MySQL实验训练4:数据库系统维护——不是背命令,而是建立“服务生命周期感知”

你有没有遇到过这样的场景:线上业务突然报错ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock',但systemctl status mysqld显示服务“active (running)”?或者凌晨三点收到告警,SHOW PROCESSLIST里堆了200多个Sleep状态连接,Threads_connected持续飙高到上限,而应用日志里只有一句模糊的“数据库连接超时”?这些都不是孤立故障,而是数据库系统维护链条上某个环节松动的信号。本实验训练4聚焦的数据库系统维护,绝非教你怎么敲mysqldump或mysqladmin flush-logs,而是带你亲手构建一套可落地、可验证、可回溯的MySQL服务健康闭环:从服务启停与状态诊断的底层逻辑,到日志轮转与空间治理的自动化边界,再到备份策略与恢复验证的可靠性校验,最后落点在连接池泄漏、慢查询堆积、表锁阻塞等真实生产问题的定位路径。它面向的是已经能建库建表、写CRUD的开发者或DBA初学者——你需要的不是“又一个教程”,而是把MySQL当成一个有心跳、有呼吸、会生病、需体检的运行中服务实体来理解与照看。文中所有操作均基于MySQL 8.0.33(社区版)在CentOS 7.9与Ubuntu 22.04双环境实测,命令、参数、错误码、日志路径全部来自真实终端输出,不抽象、不跳步、不假设你已装好Workbench或Navicat。


2. 服务启停、状态诊断与核心配置热加载:别再无脑systemctl restart mysqld

MySQL服务不是Linux进程的简单包装,它的启动过程包含初始化参数、加载插件、校验数据字典、恢复事务日志(Redo Log)等多个关键阶段。盲目重启不仅可能中断长事务、丢失未刷盘数据,更会掩盖配置错误的真实位置。本节带你用最小命令集穿透服务表象,直击内核状态。

2.1 用systemctl控制服务:理解active (exited)与active (running)的本质区别

很多初学者看到systemctl status mysqld返回active (running)就认为服务“好了”,但这是严重误解。MySQL的systemd单元文件(如/usr/lib/systemd/system/mysqld.service)通常将Type=设为simple,这意味着systemd仅监控主进程PID是否存活,完全不感知MySQL内部是否完成初始化。真实情况是:进程PID存在,但mysqld可能卡在InnoDB recovery阶段,此时mysql -u root -p连接会直接超时。

提示:判断服务是否真正就绪,必须结合两个指标:
①systemctl is-active mysqld返回active;
②mysqladmin -u root -p ping 2>/dev/null | grep "mysqld is alive"成功返回。二者缺一不可。

# 正确的服务启动与就绪验证流程(推荐封装为脚本) sudo systemctl start mysqld # 等待至多30秒,避免立即检查 sleep 5 # 循环检测,直到mysqladmin返回"mysqld is alive" for i in {1..6}; do if mysqladmin -u root -p'your_password' ping 2>/dev/null | grep -q "mysqld is alive"; then echo "✅ MySQL service is fully ready" break elif [ $i -eq 6 ]; then echo "❌ MySQL failed to become ready after 30s. Check /var/log/mysqld.log" exit 1 else sleep 5 fi done

代码说明:

  • mysqladmin ping是MySQL官方推荐的轻量级健康检查命令,它不创建连接池,只发送一个PING包并等待ACK,毫秒级响应;
  • 2>/dev/null屏蔽密码警告(生产环境应使用.my.cnf配置文件存储凭据);
  • 循环6次×5秒=30秒,覆盖InnoDB崩溃恢复(Crash Recovery)的典型耗时窗口;
  • 失败时明确指向/var/log/mysqld.log—— 这是MySQL错误日志的默认路径,所有启动失败原因必在此处记录。

2.2mysqld --help --verbose:比文档更准的配置源,动态解析my.cnf加载顺序

MySQL读取配置文件的顺序是硬编码逻辑:/etc/my.cnf→/etc/mysql/my.cnf→/usr/etc/my.cnf→~/.my.cnf。但实际生效的参数值,可能被后续文件中的同名参数覆盖,也可能被启动命令行参数强制覆盖。靠肉眼比对配置文件极易出错。

正确做法是让MySQL自己告诉你当前生效值:

# 获取当前mysqld进程实际加载的所有配置项(含默认值) mysqld --help --verbose 2>/dev/null | grep -A 1 "Default options" | tail -n +2 | head -n -1 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' # 获取特定参数的实际值(例如max_connections) mysqld --help --verbose 2>/dev/null | grep "max_connections" | head -n 1 | awk '{print $NF}' # 输出示例:200 # 验证该值是否被my.cnf显式修改(搜索所有配置文件) grep -r "max_connections" /etc/my.cnf /etc/mysql/ 2>/dev/null | grep -v "^#"

参数说明:

  • --help --verbose是MySQL内置的配置解析器,它模拟启动流程,加载所有配置文件后输出最终参数值,结果100%等同于mysqld实际运行时的值;
  • awk '{print $NF}'提取每行最后一个字段,即参数的默认值或配置值;
  • grep -r递归搜索配置文件,但必须排除注释行(grep -v "^#"),因为#max_connections=100不等于生效配置。

2.3 配置热加载:哪些参数能SET GLOBAL,哪些必须重启?

MySQL 8.0 支持约70%的动态参数在线修改,但误用SET GLOBAL修改静态参数会导致语法错误,而误信“已修改”会埋下隐患。必须严格区分两类参数:

参数类型修改方式是否需要重启典型示例验证方法
DynamicSET GLOBAL var=value否innodb_buffer_pool_size,max_connectionsSELECT @@global.max_connections;
Static修改my.cnf后重启是datadir,socket,portSELECT @@global.datadir;(重启后才变)
-- 查看某参数是否支持动态修改(MySQL 8.0+) SELECT VARIABLE_NAME, VARIABLE_SCOPE, SET_TIME FROM performance_schema.variables_info WHERE VARIABLE_NAME = 'max_connections'; -- 输出:VARIABLE_NAME='max_connections', VARIABLE_SCOPE='GLOBAL', SET_TIME='DYNAMIC' -- 表明支持SET GLOBAL -- 安全修改max_connections(需SUPER权限) SET GLOBAL max_connections = 500; -- 立即生效,无需重启 -- 尝试修改静态参数(会报错) SET GLOBAL datadir = '/new/path'; -- ERROR 1238 (HY000): Variable 'datadir' is a read only variable

关键逻辑:

  • performance_schema.variables_info是MySQL 8.0引入的权威元数据表,VARIABLE_SCOPE='DYNAMIC'是唯一可信依据;
  • SET GLOBAL修改仅对新建立的连接生效,已存在的连接仍使用旧值(@@session.var不变);
  • 所有SET GLOBAL修改在MySQL重启后丢失,必须同步写入my.cnf才能持久化。

3. 错误日志、慢查询日志与二进制日志的精细化治理:日志不是垃圾桶,是事故黑匣子

日志是数据库维护的“行车记录仪”。但默认配置下,错误日志永不清除、慢查询日志不记录执行计划、二进制日志不自动过期——这会导致磁盘爆满、分析效率低下、恢复窗口失控。本节教你按生产标准配置三类核心日志。

3.1 错误日志(Error Log):精准定位启动失败与运行时异常

MySQL错误日志默认路径为/var/log/mysqld.log(RHEL系)或/var/log/mysql/error.log(Debian系)。但默认配置log_error_verbosity=3会记录大量调试信息,日志体积暴涨且干扰关键错误。

生产级配置(添加到/etc/my.cnf的[mysqld]段):

[mysqld] # 指定错误日志路径(必须绝对路径,且MySQL用户有写权限) log_error = /var/log/mysql/error.log # 日志级别:1=error, 2=error+warning, 3=error+warning+note(生产建议设为2) log_error_verbosity = 2 # 启用日志轮转(MySQL 8.0.14+),避免单文件过大 log_error_services = 'log_filter_internal; log_sink_sysevent; log_sink_json' # 配置logrotate(需额外创建/etc/logrotate.d/mysql) # /var/log/mysql/error.log { # daily # missingok # rotate 30 # compress # delaycompress # notifempty # create 640 mysql mysql # sharedscripts # postrotate # if systemctl is-active --quiet mysqld; then # mysql -u root -p'pwd' -e "FLUSH ERROR LOGS;" # fi # endscript # }

参数说明:

  • log_error_verbosity=2过滤掉Note级别日志(如“Starting crash recovery”),聚焦Error和Warning;
  • log_error_services启用JSON格式日志(log_sink_json),便于ELK等工具解析;
  • logrotate配置中postrotate脚本调用FLUSH ERROR LOGS命令,通知MySQL关闭旧日志文件句柄,确保logrotate能安全重命名。

注意:FLUSH ERROR LOGS在MySQL 8.0.14+才支持。低于此版本需用kill -USR1 $(cat /var/run/mysqld/mysqld.pid)发送信号触发日志轮转。

3.2 慢查询日志(Slow Query Log):不只是记录SQL,更要捕获执行计划与锁等待

默认慢查询日志(slow_query_log=ON)只记录SQL文本,无法分析为何慢。生产必须开启log_slow_extra=ON(MySQL 8.0.26+),它会追加Query_time,Lock_time,Rows_sent,Rows_examined,Tmp_tables,Tmp_disk_tables,Full_scan等关键指标,并支持log_slow_admin_statements记录ALTER TABLE等管理语句。

[mysqld] # 启用慢查询日志 slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log # 慢查询阈值:超过2秒记为慢查询(根据业务调整) long_query_time = 2.0 # 记录未使用索引的查询(即使执行快,也暴露设计缺陷) log_queries_not_using_indexes = ON # 【关键】启用扩展信息(MySQL 8.0.26+) log_slow_extra = ON # 记录管理语句(如OPTIMIZE、ANALYZE) log_slow_admin_statements = ON # 日志轮转(同错误日志) logrotate配置中添加/var/log/mysql/slow.log条目,并在postrotate中执行: # mysql -e "FLUSH SLOW LOGS;"

验证慢查询日志是否生效:

-- 强制触发一条慢查询(睡眠2.5秒) SELECT SLEEP(2.5); -- 查看慢查询日志内容(需有文件读取权限) sudo tail -n 20 /var/log/mysql/slow.log -- 输出示例(含扩展字段): # Time: 2024-05-20T08:12:33.456789Z # User@Host: root[root] @ localhost [] Id: 7 # Query_time: 2.500123 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 1 # Full_scan: No Tmp_tables: 0 Tmp_disk_tables: 0 # SET timestamp=1716192753; # SELECT SLEEP(2.5);

关键字段解读:

  • Query_time=2.500123:实际执行耗时,精确到微秒;
  • Rows_examined=1:扫描1行,说明无性能问题,只是人为延迟;
  • Full_scan: No:未触发全表扫描;
  • 若出现Rows_examined=1000000且Rows_sent=1,则表明SQL存在严重索引缺失。

3.3 二进制日志(Binary Log):设置合理过期策略,保障主从同步与PITR能力

二进制日志是数据恢复(Point-in-Time Recovery, PITR)和主从复制的基石。但默认expire_logs_days=0(永不过期),极易撑爆磁盘。必须设置binlog_expire_logs_seconds(MySQL 8.0.28+)或expire_logs_days。

[mysqld] # 启用二进制日志(必须) log_bin = /var/lib/mysql/mysql-bin # 设置服务器ID(主从复制必需) server_id = 1 # 【关键】设置二进制日志过期时间(推荐7天) # MySQL 8.0.28+ 使用秒级精度(更精确) binlog_expire_logs_seconds = 604800 # 7*24*3600 # 低于8.0.28版本使用天数 # expire_logs_days = 7 # 限制单个binlog文件大小(避免单文件过大影响传输) max_binlog_size = 100M # 启用GTID(全局事务ID),简化主从管理 gtid_mode = ON enforce_gtid_consistency = ON

验证与清理:

# 查看当前二进制日志列表及文件大小 mysql -e "SHOW BINARY LOGS;" # 手动清理过期日志(立即生效) mysql -e "PURGE BINARY LOGS BEFORE '2024-05-13 00:00:00';" # 或按文件名清理(更安全) mysql -e "PURGE BINARY LOGS TO 'mysql-bin.000015';" # 查看剩余日志文件磁盘占用 sudo du -sh /var/lib/mysql/mysql-bin.*

避坑原则:

  • PURGE BINARY LOGS命令会永久删除日志文件,执行前务必确认主从复制已同步到目标位置(SHOW SLAVE STATUS\G中Relay_Master_Log_File和Exec_Master_Log_Pos);
  • binlog_expire_logs_seconds是后台线程定时清理,PURGE是手动干预,二者不冲突;
  • 若使用Xtrabackup等物理备份,binlog_expire_logs_seconds必须大于备份恢复所需的最大时间窗口。

4. 数据库备份与恢复验证:备份有效性的唯一标准是成功恢复

“我有备份”不等于“我能恢复”。无数事故源于备份文件损坏、权限错误、或恢复步骤遗漏。本节提供可验证、可审计、可自动化的备份方案。

4.1 逻辑备份:mysqldump的生产级参数组合与增量备份链

mysqldump是最通用的逻辑备份工具,但默认参数在生产环境极不安全。必须启用以下关键选项:

# 生产级全库备份命令(含GTID、一致性、压缩) mysqldump \ --user=root \ --password='your_pwd' \ --host=localhost \ --port=3306 \ --single-transaction \ # InnoDB一致性快照(不锁表) --routines \ # 导出存储过程 --triggers \ # 导出触发器 --events \ # 导出事件调度器 --set-gtid-purged=ON \ # 保留GTID信息,便于PITR --master-data=2 \ # 记录备份时的binlog位置(用于PITR) --hex-blob \ # 防止BLOB字段乱码 --skip-comments \ # 移除注释,减小体积 --skip-triggers \ # 若触发器有依赖问题,可临时跳过 --databases db1 db2 | gzip > /backup/full_$(date +%Y%m%d_%H%M%S).sql.gz # 验证备份文件完整性(解压并检查前10行) gunzip -c /backup/full_20240520_080000.sql.gz | head -n 10

参数深度解析:

  • --single-transaction:对InnoDB表开启一致性快照,全程不加任何表锁,是OLTP系统备份的黄金标准;
  • --set-gtid-purged=ON:在dump文件开头插入SET @@GLOBAL.GTID_PURGED='xxx',恢复时自动设置GTID,避免主从冲突;
  • --master-data=2:在dump文件中插入CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000012', MASTER_LOG_POS=123456;,这是PITR的起点坐标;
  • gzip压缩可减少70%体积,但恢复时需先解压,切勿用zcat管道恢复(会因管道阻塞导致超时)。

4.2 物理备份:Percona XtraBackup 8.0 的免锁全量+增量备份

当数据库超100GB时,mysqldump的逻辑导出速度成为瓶颈。XtraBackup提供真正的热备份(Hot Backup),备份期间业务零感知。

# 1. 全量备份(首次) xtrabackup --user=root --password='pwd' \ --backup \ --target-dir=/backup/xtrabackup/full_$(date +%Y%m%d) \ --parallel=4 \ --compress \ --compress-threads=2 # 2. 增量备份(基于上次全量) xtrabackup --user=root --password='pwd' \ --backup \ --target-dir=/backup/xtrabackup/inc_$(date +%Y%m%d_%H%M%S) \ --incremental-based-dir=/backup/xtrabackup/full_20240520 \ --parallel=4 \ --compress # 3. 准备备份(Apply log,使备份可启动) xtrabackup --prepare \ --apply-log-only \ --target-dir=/backup/xtrabackup/full_20240520 xtrabackup --prepare \ --apply-log-only \ --target-dir=/backup/xtrabackup/full_20240520 \ --incremental-dir=/backup/xtrabackup/inc_20240521_120000 # 4. 恢复(停止MySQL,清空datadir,拷贝备份,修复权限) sudo systemctl stop mysqld sudo rm -rf /var/lib/mysql/* sudo xtrabackup --copy-back \ --target-dir=/backup/xtrabackup/full_20240520 sudo chown -R mysql:mysql /var/lib/mysql sudo systemctl start mysqld

关键逻辑:

  • --apply-log-only对全量备份和增量备份都必须使用,它只重放Redo Log,不回滚未提交事务,确保增量链完整;
  • 最后一次--prepare(不加--apply-log-only)才执行回滚,生成可启动的备份;
  • --copy-back必须由root执行,但恢复后/var/lib/mysql目录所有权必须为mysql:mysql,否则mysqld拒绝启动。

4.3 恢复验证:自动化脚本验证备份可用性

备份有效性验证必须自动化,不能靠人工mysql < backup.sql。以下脚本在独立测试实例中执行恢复,并运行校验SQL:

#!/bin/bash # validate_backup.sh BACKUP_FILE="/backup/full_$(date -d 'yesterday' +%Y%m%d)_*.sql.gz" TEST_MYSQL_PORT=3307 # 1. 启动独立测试MySQL实例(使用不同端口和datadir) sudo mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql_test sudo mysqld --user=mysql --datadir=/var/lib/mysql_test --port=$TEST_MYSQL_PORT & # 2. 等待测试实例就绪 for i in {1..10}; do if mysqladmin -u root -P $TEST_MYSQL_PORT ping 2>/dev/null; then break fi sleep 3 done # 3. 恢复备份 gunzip -c $BACKUP_FILE | mysql -u root -P $TEST_MYSQL_PORT # 4. 运行校验SQL(检查关键表行数) COUNT=$(mysql -u root -P $TEST_MYSQL_PORT -Nse "SELECT COUNT(*) FROM db1.users;") if [ "$COUNT" -gt 0 ]; then echo "✅ Backup validation PASSED: db1.users has $COUNT rows" else echo "❌ Backup validation FAILED: db1.users is empty" exit 1 fi # 5. 清理测试实例 sudo mysqladmin -u root -P $TEST_MYSQL_PORT shutdown sudo rm -rf /var/lib/mysql_test

执行频率:建议每日凌晨执行一次,将结果写入日志并邮件告警。没有通过验证的备份,等同于没有备份。


5. 连接泄漏、慢查询与锁阻塞的实时定位:从SHOW PROCESSLIST到performance_schema

当业务报警“数据库慢”,第一反应不该是重启,而是用MySQL自带的诊断工具定位根因。本节提供一套标准化排查路径。

5.1 连接数暴增:识别连接池泄漏与未关闭连接

Threads_connected持续高于max_connections的80%,是连接泄漏的明确信号。但SHOW PROCESSLIST只显示当前连接,需结合performance_schema追踪源头。

-- 1. 查看当前所有连接及其状态 SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' ORDER BY TIME DESC LIMIT 20; -- 2. 深度分析:谁在创建最多连接?(需提前开启performance_schema) SELECT p.HOST, p.USER, COUNT(*) as conn_count, MAX(p.TIME) as max_idle_time_sec FROM performance_schema.threads t JOIN performance_schema.processlist p ON t.PROCESSLIST_ID = p.ID WHERE p.COMMAND = 'Sleep' GROUP BY p.HOST, p.USER ORDER BY conn_count DESC LIMIT 10;

现象与根因:

  • 若HOST列显示大量192.168.1.100:54321(应用服务器IP+随机端口),且USER为app_user,说明应用层连接池未配置最大连接数或未启用连接回收;
  • max_idle_time_sec过高(如>3600秒)表明连接空闲超时未释放,需检查应用连接池配置(如HikariCP的idle-timeout)。

5.2 慢查询定位:从slow.log到EXPLAIN ANALYZE

慢查询日志只告诉你“哪条SQL慢”,EXPLAIN ANALYZE(MySQL 8.0.18+)告诉你“为什么慢”。

-- 1. 从slow.log提取慢SQL(示例) # Query_time: 5.234567 Lock_time: 0.000123 Rows_sent: 1 Rows_examined: 1000000 # SELECT * FROM orders WHERE status='pending' AND created_at < '2024-01-01'; -- 2. 在MySQL中执行EXPLAIN ANALYZE(真实执行并返回执行计划) EXPLAIN ANALYZE SELECT * FROM orders WHERE status='pending' AND created_at < '2024-01-01'; -- 输出关键字段: -- -> Filter: ((orders.status = 'pending') and (orders.created_at < TIMESTAMP'2024-01-01 00:00:00')) (cost=12345.67 rows=1000000) -- -> Table scan on orders (cost=10000.00 rows=1000000) -- -> Rows matched: 1000000 Time: 5.234s

解读:

  • Table scan on orders表明全表扫描,Rows matched: 1000000证实扫描了全部100万行;
  • Filter行显示WHERE条件在扫描后过滤,而非利用索引;
  • 解决方案:为(status, created_at)创建联合索引:
    CREATE INDEX idx_status_created ON orders(status, created_at);

5.3 锁阻塞分析:sys.innodb_lock_waits视图精确定位死锁源头

当SHOW PROCESSLIST中出现大量Locked状态,或应用报Lock wait timeout exceeded,需立即分析锁。

-- 1. 查看当前锁等待关系(MySQL 8.0+ sys schema) SELECT w.trx_id waiting_trx_id, w.trx_mysql_thread_id waiting_thread, w.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM sys.innodb_lock_waits w JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id JOIN information_schema.INNODB_TRX w2 ON w2.trx_id = w.waiting_trx_id; -- 2. 查看阻塞线程持有的锁(定位具体行) SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, rl.lock_table, rl.lock_index, rl.lock_data FROM information_schema.INNODB_LOCK_WAITS w JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id JOIN information_schema.INNODB_LOCKS rl ON rl.lock_trx_id = b.trx_id;

典型场景与解决:

  • 若lock_table为test.orders,lock_index为PRIMARY,lock_data为12345,表明事务在更新orders表主键为12345的行;
  • blocking_query显示UPDATE orders SET status='shipped' WHERE id=12345;,而waiting_query是UPDATE orders SET amount=100 WHERE id=12345;,构成行锁冲突;
  • 根治:应用层对同一业务主键的更新操作,必须保证事务内执行顺序一致,或使用SELECT ... FOR UPDATE显式加锁。

6. 维护脚本自动化与健康巡检:把经验固化为可执行的SOP

手工执行维护命令易遗漏、难追溯。本节提供一个生产就绪的MySQL健康巡检脚本,每日自动运行并生成报告。

6.1mysql_health_check.sh:覆盖磁盘、连接、复制、日志的7项核心检查

#!/bin/bash # mysql_health_check.sh - Production Health Check Script DATE=$(date +%Y%m%d_%H%M%S) REPORT="/var/log/mysql/health_report_${DATE}.log" MYSQL_CMD="mysql -u root -p'your_pwd' -Nse" echo "=== MySQL Health Check Report: $(date) ===" > $REPORT # 1. 磁盘空间检查(datadir所在分区) echo "1. Disk Space:" >> $REPORT df -h /var/lib/mysql | tee -a $REPORT # 2. 连接数检查 echo -e "\n2. Connection Status:" >> $REPORT CONNS=$($MYSQL_CMD "SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Threads_connected';") MAX_CONNS=$($MYSQL_CMD "SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='max_connections';") USAGE_PCT=$((CONNS * 100 / MAX_CONNS)) echo "Current: ${CONNS}, Max: ${MAX_CONNS}, Usage: ${USAGE_PCT}%" >> $REPORT if [ $USAGE_PCT -gt 80 ]; then echo "⚠️ WARNING: Connection usage > 80%" >> $REPORT fi # 3. 主从复制状态检查(仅主库) echo -e "\n3. Replication Status:" >> $REPORT SLAVE_STATUS=$($MYSQL_CMD "SHOW SLAVE STATUS\G" 2>/dev/null | head -n 1) if [ -z "$SLAVE_STATUS" ]; then echo "Not a slave server" >> $REPORT else IO_RUNNING=$($MYSQL_CMD "SHOW SLAVE STATUS\G" 2>/dev/null | grep "Slave_IO_Running:" | awk '{print $2}') SQL_RUNNING=$($MYSQL_CMD "SHOW SLAVE STATUS\G" 2>/dev/null | grep "Slave_SQL_Running:" | awk '{print $2}') echo "IO Running: ${IO_RUNNING}, SQL Running: ${SQL_RUNNING}" >> $REPORT if [ "$IO_RUNNING" != "Yes" ] || [ "$SQL_RUNNING" != "Yes" ]; then echo "❌ REPLICATION BROKEN!" >> $REPORT fi fi # 4. 二进制日志过期检查 echo -e "\n4. Binary Log Expiration:" >> $REPORT EXPIRE_SEC=$($MYSQL_CMD "SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='binlog_expire_logs_seconds';" 2>/dev/null) if [ -z "$EXPIRE_SEC" ]; then EXPIRE_DAYS=$($MYSQL_CMD "SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME='expire_logs_days';" 2>/dev/null) echo "expire_logs_days = ${EXPIRE_DAYS}" >> $REPORT else echo "binlog_expire_logs_seconds = ${EXPIRE_SEC} (${EXPIRE_SEC} seconds)" >> $REPORT fi # 5. 错误日志最近1小时错误数 echo -e "\n5. Recent Errors (last 1h):" >> $REPORT ERROR_COUNT=$(sudo grep -c "$(date -d '1 hour ago' '+%Y-%m-%dT%H')" /var/log/mysql/error.log 2>/dev/null || echo 0) echo "Error count in last 1h: ${ERROR_COUNT}" >> $REPORT if [ $ERROR_COUNT -gt 5 ]; then echo "⚠️ WARNING: More than 5 errors in last hour" >> $REPORT fi # 6. 慢查询日志活跃度 echo -e "\n6. Slow Query Log Activity:" >> $REPORT SLOW_COUNT=$(sudo tail -n 1000 /var/log/mysql/slow.log 2>/dev/null | grep -c "^# Time:" || echo 0) echo "Slow queries in last 1000 lines: ${SLOW_COUNT}" >> $REPORT # 7. 表碎片率检查(top 5) echo -e "\n7. Top 5 Fragmented Tables:" >> $REPORT $MYSQL_CMD " SELECT CONCAT(table_schema,'.',table_name) as table_name, ROUND(data_free/1024/1024,2) as data_free_mb, ROUND((data_free/(data_length+index_length))*100,2) as frag_pct FROM information_schema.tables WHERE table_schema NOT IN ('information_schema','mysql','performance_schema','sys') AND data_free > 0 ORDER BY data_free DESC LIMIT 5; " 2>/dev/null | tee -a $REPORT # 发送报告到邮箱(需配置mailx) # echo "MySQL Health Report for $(hostname)" | mailx -s "MySQL Health $(date +%Y-%m-%d)" admin@example.com < $REPORT echo "✅ Health check completed. Report saved to $REPORT"

脚本特点:

  • 所有检查项均有明确阈值(如连接数>80%、错误数>5)和分级告警(✅/⚠️/❌);
  • 输出格式统一,可直接用grep提取关键指标做监控集成;
  • 注释掉的邮件发送行,可根据企业邮件系统(如Postfix)取消注释启用。

6.2 定时任务配置:crontab每日02:00执行巡检

# 编辑root用户的crontab sudo crontab -e # 添加以下行(每日凌晨2 <p> <a href="https://download.csdn.net/download/weixin_45237395/12546551" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询