1. MySQL运维自动化:Shell脚本的价值与应用场景
作为一名长期奋战在数据库运维一线的工程师,我深知日常工作中那些看似简单的重复操作是如何消耗宝贵时间的。每次手动连接服务器、执行查询、记录结果,不仅效率低下,还容易因疲劳导致误操作。而Shell脚本正是解决这一痛点的利器。
Shell脚本在MySQL运维中的核心价值主要体现在三个方面:首先,它能将重复性操作固化下来,避免人工操作的不稳定性;其次,通过定时任务可以实现7×24小时无人值守监控;最后,脚本执行过程天然具备可追溯性,所有操作都有日志可查。
在实际工作中,我总结了几个最适合脚本化的高频场景:
- 数据库健康检查(连接数、慢查询、锁等待等)
- 定期数据备份与恢复验证
- 批量修改表结构或数据
- 多实例统一配置管理
- 性能指标采集与分析
重要提示:生产环境使用脚本时,务必遵循最小权限原则,像示例中专门创建shell_rw用户的做法就非常值得借鉴。绝对不要为了省事直接使用root账户。
2. 环境准备与基础操作
2.1 安全配置最佳实践
创建专用运维账户时,权限分配需要精细控制。以下是改进后的授权方案:
CREATE USER 'monitor_user'@'192.168.%' IDENTIFIED BY 'ComplexPwd123!'; GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO 'monitor_user'@'192.168.%'; FLUSH PRIVILEGES;这样配置的优势在于:
- 限制访问来源IP段(192.168.%)
- 只授予必要的只读权限
- 包含PROCESS权限用于查看连接信息
- 包含REPLICATION CLIENT用于监控主从状态
2.2 测试数据准备进阶技巧
创建测试表时,建议增加更多真实场景的字段和索引:
CREATE TABLE products ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT '商品名称', category ENUM('电子','食品','服饰') NOT NULL DEFAULT '电子', price DECIMAL(10,2) NOT NULL, stock INT UNSIGNED NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_category (category), INDEX idx_price (price) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这样的表结构可以模拟更真实的业务场景,方便后续测试各种查询和更新操作。
3. Shell脚本基础实战
3.1 健壮的CRUD脚本实现
原始脚本存在密码明文存储的安全隐患,改进后的版本:
#!/bin/bash # 配置文件方式存储敏感信息 CONFIG_FILE="/etc/mysql_script.conf" if [ ! -f "$CONFIG_FILE" ]; then echo "❌ 错误: 配置文件 $CONFIG_FILE 不存在" exit 1 fi source "$CONFIG_FILE" # 检查必要参数 for var in MYSQL_HOST MYSQL_USER MYSQL_PASS MYSQL_DB; do if [ -z "${!var}" ]; then echo "❌ 错误: 配置参数 $var 未设置" exit 1 fi done # 使用临时文件存储查询结果 TMP_FILE=$(mktemp) # 异常处理函数 cleanup() { [ -f "$TMP_FILE" ] && rm -f "$TMP_FILE" } trap cleanup EXIT # 执行SQL函数 execute_sql() { local sql="$1" local label="$2" echo "【$label】执行: $sql" mysql -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" -e "$sql" > "$TMP_FILE" 2>&1 if [ $? -ne 0 ]; then echo "❌ 执行失败:" cat "$TMP_FILE" return 1 fi echo "✅ 执行结果:" cat "$TMP_FILE" return 0 } # 主流程 execute_sql "INSERT INTO products (name, price) VALUES ('apple', 10.50)" "插入数据" execute_sql "SELECT * FROM products WHERE name='apple'" "查询数据" execute_sql "UPDATE products SET price=12.5 WHERE name='apple'" "更新数据" execute_sql "DELETE FROM products WHERE name='apple'" "删除数据"改进点包括:
- 敏感信息存储在单独配置文件中
- 增加参数检查
- 使用临时文件处理输出
- 完善的错误处理机制
- 函数化重复代码
3.2 连接池化技术
对于需要频繁连接MySQL的脚本,建议使用连接池技术提高性能:
# 使用mysqlsh实现连接池 #!/bin/bash # 初始化连接 mysqlsh --uri="$MYSQL_USER@$MYSQL_HOST" --password="$MYSQL_PASS" --schema="$MYSQL_DB" --js <<EOF // 执行多个查询 session.sql('SELECT COUNT(*) FROM products').execute(); session.sql('SHOW TABLE STATUS').execute(); EOF4. 高级运维脚本开发
4.1 连接监控增强版
原始processlist脚本的增强版本:
#!/bin/bash # 参数化配置 INTERVAL=${1:-10} # 默认监控10秒 MAX_CONNECTIONS=${2:-100} while true; do TIMESTAMP=$(date +"%Y%m%d%H%M%S") LOG_FILE="connections_${TIMESTAMP}.log" # 获取完整processlist mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e " SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, SUBSTRING(INFO, 1, 100) AS QUERY_PREVIEW FROM information_schema.PROCESSLIST ORDER BY TIME DESC; " > "$LOG_FILE" # 分析连接数 TOTAL_CONNS=$(wc -l < "$LOG_FILE") ACTIVE_CONNS=$(grep -c -v "Sleep" "$LOG_FILE") echo "[$(date '+%Y-%m-%d %H:%M:%S')] 总连接数: $TOTAL_CONNS, 活跃连接: $ACTIVE_CONNS" # 预警机制 if [ "$TOTAL_CONNS" -gt "$MAX_CONNECTIONS" ]; then echo "⚠️ 警告: 连接数超过阈值($MAX_CONNECTIONS)" | tee -a "$LOG_FILE" # 这里可以添加邮件/短信报警逻辑 fi sleep "$INTERVAL" done新增功能:
- 可配置监控间隔
- 连接数阈值预警
- 查询内容截取展示
- 活跃连接统计
- 持续监控模式
4.2 多服务器管理模板
批量查询脚本的模板化改进:
#!/bin/bash # 服务器清单文件格式 # IP,USER,PORT,PASSWORD,DESCRIPTION SERVER_LIST="servers.csv" OUTPUT_DIR="reports" mkdir -p "$OUTPUT_DIR" # 检查依赖 require_cmd() { if ! command -v "$1" >/dev/null; then echo "❌ 需要安装 $1" exit 1 fi } require_cmd mysql require_cmd csvtool # 主流程 while IFS=, read -r ip user port password description; do echo "正在检查服务器: $description ($ip)" REPORT_FILE="${OUTPUT_DIR}/${ip}_report_$(date +%Y%m%d).txt" # 检查连通性 if ! nc -z -w 3 "$ip" "$port"; then echo "❌ 无法连接 $ip:$port" | tee "$REPORT_FILE" continue fi # 收集关键指标 { echo "===== 服务器: $description ($ip) =====" echo "1. 基础信息" mysql -h "$ip" -P "$port" -u "$user" -p"$password" -e "SELECT @@version, @@hostname" echo "\n2. 关键参数" mysql -h "$ip" -P "$port" -u "$user" -p"$password" -e " SHOW GLOBAL VARIABLES WHERE Variable_name IN ('max_connections','innodb_buffer_pool_size','sync_binlog'); " echo "\n3. 运行状态" mysql -h "$ip" -P "$port" -u "$user" -p"$password" -e " SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Queries'); " } > "$REPORT_FILE" echo "✅ 报告已生成: $REPORT_FILE" done < <(csvtool namedcol "IP,USER,PORT,PASSWORD,DESCRIPTION" "$SERVER_LIST")5. 性能监控与优化
5.1 实时性能监控脚本
#!/bin/bash # 监控配置 INTERVAL=5 DURATION=60 # 检查InnoDB状态函数 check_innodb_status() { mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e " SHOW ENGINE INNODB STATUS\G " > innodb_status.log # 分析锁等待 grep -A 10 "LATEST DETECTED DEADLOCK" innodb_status.log grep "lock wait" innodb_status.log | wc -l } # 主监控循环 end_time=$((SECONDS + DURATION)) while [ $SECONDS -lt $end_time ]; do # 获取基础指标 metrics=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e " SHOW GLOBAL STATUS WHERE Variable_name IN ( 'Threads_connected', 'Threads_running', 'Queries', 'Slow_queries', 'Innodb_row_lock_waits' ); ") # 处理指标 threads_connected=$(echo "$metrics" | grep "Threads_connected" | awk '{print $2}') queries_per_sec=$(echo "$metrics" | grep "Queries" | awk '{print $2}') # 输出监控信息 echo "[$(date '+%H:%M:%S')] 连接数: $threads_connected, QPS: $queries_per_sec" # 异常检测 if [ "$threads_connected" -gt 100 ]; then check_innodb_status fi sleep $INTERVAL done5.2 自动化索引分析
#!/bin/bash # 参数检查 if [ -z "$1" ]; then echo "使用方法: $0 <数据库名>" exit 1 fi DB_NAME="$1" # 生成索引分析报告 REPORT_FILE="index_report_${DB_NAME}_$(date +%Y%m%d).html" # 使用CSS美化报告 cat <<EOF > "$REPORT_FILE" <html> <head> <style> body { font-family: Arial, sans-serif; margin: 20px; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } tr:nth-child(even) { background-color: #f2f2f2; } .warning { background-color: #fff3cd; } .danger { background-color: #f8d7da; } </style> <title>MySQL索引分析报告 - $DB_NAME</title> </head> <body> <h1>MySQL索引分析报告 - $DB_NAME</h1> <p>生成时间: $(date)</p> EOF # 查询缺少索引的表 mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" "$DB_NAME" -e " SELECT t.TABLE_NAME, t.TABLE_ROWS, COUNT(i.INDEX_NAME) AS INDEX_COUNT, ROUND(t.DATA_LENGTH/1024/1024,2) AS SIZE_MB FROM information_schema.TABLES t LEFT JOIN information_schema.STATISTICS i ON t.TABLE_SCHEMA = i.TABLE_SCHEMA AND t.TABLE_NAME = i.TABLE_NAME WHERE t.TABLE_SCHEMA = '$DB_NAME' GROUP BY t.TABLE_NAME, t.TABLE_ROWS, t.DATA_LENGTH HAVING INDEX_COUNT <= 1 ORDER BY t.TABLE_ROWS DESC; " >> "$REPORT_FILE" echo "</body></html>" >> "$REPORT_FILE" echo "索引分析报告已生成: $REPORT_FILE"6. 安全与维护最佳实践
6.1 自动化备份方案
#!/bin/bash # 备份配置 BACKUP_DIR="/data/mysql_backups" KEEP_DAYS=7 TIMESTAMP=$(date +"%Y%m%d_%H%M%S") # 创建备份目录 mkdir -p "$BACKUP_DIR" # 获取数据库列表 DATABASES=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW DATABASES;" | grep -Ev "(Database|information_schema|performance_schema|mysql|sys)") # 备份函数 backup_database() { local db="$1" local backup_file="${BACKUP_DIR}/${db}_${TIMESTAMP}.sql.gz" echo "备份数据库: $db" mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASS" \ --single-transaction \ --routines \ --triggers \ --events \ "$db" | gzip > "$backup_file" # 验证备份 if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "❌ 备份失败: $db" rm -f "$backup_file" return 1 fi echo "✅ 备份成功: $(ls -lh "$backup_file")" return 0 } # 主备份流程 for db in $DATABASES; do backup_database "$db" done # 清理旧备份 find "$BACKUP_DIR" -name "*.sql.gz" -mtime +"$KEEP_DAYS" -delete6.2 用户权限审计脚本
#!/bin/bash # 生成用户权限报告 REPORT_FILE="mysql_user_audit_$(date +%Y%m%d).csv" # CSV表头 echo "用户名,主机,全局权限,数据库权限,表权限,列权限" > "$REPORT_FILE" # 查询用户权限 mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e " SELECT CONCAT('\'', user, '\'@\'', host, '\'') AS user_host, IFNULL(global_privs, '') AS global_privs, IFNULL(db_privs, '') AS db_privs, IFNULL(table_privs, '') AS table_privs, IFNULL(column_privs, '') AS column_privs FROM ( SELECT u.user, u.host, (SELECT GROUP_CONCAT(privilege_type) FROM information_schema.user_privileges WHERE grantee = CONCAT('\'', u.user, '\'@\'', u.host, '\'')) AS global_privs, (SELECT GROUP_CONCAT(CONCAT(table_schema, ':', privilege_type)) FROM information_schema.schema_privileges WHERE grantee = CONCAT('\'', u.user, '\'@\'', u.host, '\'') GROUP BY grantee) AS db_privs, (SELECT GROUP_CONCAT(CONCAT(table_schema, '.', table_name, ':', privilege_type)) FROM information_schema.table_privileges WHERE grantee = CONCAT('\'', u.user, '\'@\'', u.host, '\'') GROUP BY grantee) AS table_privs, (SELECT GROUP_CONCAT(CONCAT(table_schema, '.', table_name, '.', column_name, ':', privilege_type)) FROM information_schema.column_privileges WHERE grantee = CONCAT('\'', u.user, '\'@\'', u.host, '\'') GROUP BY grantee) AS column_privs FROM mysql.user u WHERE u.user NOT IN ('mysql.sys','mysql.session','mysql.infoschema') ) t ORDER BY user, host; " >> "$REPORT_FILE" echo "用户权限审计报告已生成: $REPORT_FILE"7. 脚本维护与优化建议
在实际使用这些脚本的过程中,我总结了以下几点经验:
日志记录标准化:所有脚本都应该实现统一的日志格式,建议包含:
- 时间戳
- 脚本名称
- 执行结果(成功/失败)
- 关键指标数值
- 错误信息(如失败)
参数化设计:好的运维脚本应该做到:
- 所有配置参数外部化
- 支持命令行参数覆盖
- 提供--help帮助信息
- 有默认值但可配置
错误处理三原则:
- 失败立即终止(set -e)
- 错误信息明确指向问题根源
- 提供修复建议
性能考虑:
- 批量操作使用事务
- 避免在循环中频繁连接数据库
- 大数据量操作添加进度显示
安全加固:
- 密码等敏感信息加密存储
- 配置文件设置严格权限
- 操作前进行二次确认
一个典型的改进示例:
#!/bin/bash # 增强版脚本模板 set -euo pipefail # 配置加载 CONFIG_FILE="${SCRIPT_DIR}/config.cfg" [ -f "$CONFIG_FILE" ] && source "$CONFIG_FILE" # 日志函数 log() { local level="$1" local message="$2" echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE" } # 主流程 main() { log "INFO" "脚本启动" # 业务逻辑 if [ ! -d "$BACKUP_DIR" ]; then log "ERROR" "备份目录不存在: $BACKUP_DIR" exit 1 fi # ... log "INFO" "脚本执行完成" } # 异常处理 trap 'log "ERROR" "脚本异常退出"; exit 1' ERR trap 'log "INFO" "脚本被用户中断"; exit 2' INT TERM main "$@"通过持续优化脚本的健壮性和可维护性,可以确保这些自动化工具长期稳定地为运维工作服务。