Linux下Docker Compose里运行的MySQL数据库故障诊断Shell脚本

将以下脚本保存为 mysql_docker_diag.sh ,在 MySQL 所在服务器的 docker-compose 项目目录root执行。

bash 复制代码
chmod +x mysql_docker_diag.sh
# 使用默认容器名mysql
sudo ./mysql_docker_diag.sh
# 指定自定义容器名
sudo ./mysql_docker_diag.sh mysql_db
bash 复制代码
#!/usr/bin/env bash
#===============================================================================
# MySQL Docker Compose 故障系统化诊断脚本 (哲学增强完整版)
# 用途:Docker Compose部署MySQL容器崩溃/异常运行全链路诊断
# 使用方式1(完整执行):chmod +x mysql_docker_diag.sh && sudo ./mysql_docker_diag.sh [容器名|默认mysql]
# 使用方式2(逐段复制终端执行):复制【单段可独立运行】标记内代码,直接粘贴终端执行
# 核心增强:
# 1. 全局缓存容器CID/项目名/数据卷路径,红色高亮一键复制
# 2. 所有修复、排查命令自动填充变量,无需手动修改名称路径
# 3. 自动输出归档报告,统一汇总所有可复制ID/路径到key_copy.txt
# 哲学思想:本体论 -> 奥卡姆剃刀 -> 整体论 -> 双轨恢复/无为自诊
#===============================================================================
set -uo pipefail

# -----------------------------【单段可独立运行】全局常量、颜色、工具函数、归档配置 -----------------------------
# 入参与基础配置
CONTAINER_INPUT="${1:-mysql}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 诊断归档目录,存放所有日志快照、可复制变量清单
DIAG_OUT_DIR="/tmp/mysql_docker_diag_${TIMESTAMP}"
mkdir -p "${DIAG_OUT_DIR}"
# 所有可复制关键ID/路径统一存入该文件,一条命令查看全部
KEY_COPY_FILE="${DIAG_OUT_DIR}/key_copy_info.txt"
> "${KEY_COPY_FILE}"

# 颜色输出定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'

# 日志打印封装
section() { echo -e "\n${CYAN}========== $1 ==========${NC}"; echo -e "\n【诊断模块】$1" >> "${KEY_COPY_FILE}"; }
info()    { echo -e "${GREEN}[INFO]${NC} $1"; echo "[正常] $1" >> "${KEY_COPY_FILE}"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; echo "[警告] $1" >> "${KEY_COPY_FILE}"; }
error()   { echo -e "${RED}[ERROR]${NC} $1"; echo "[严重异常] $1" >> "${KEY_COPY_FILE}"; }

# 核心工具:打印红色高亮可复制变量,自动写入清单
print_copy() {
    local label="$1"
    local val="$2"
    echo -e "${WHITE}【可复制 $label 】: ${RED}$val${NC}"
    echo "$label = $val" >> "${KEY_COPY_FILE}"
}
# 输出预填充变量、可直接终端执行的完整命令
print_cmd() {
    echo -e "${CYAN}[一键执行命令]${NC} $1"
    echo "执行命令:$1" >> "${KEY_COPY_FILE}"
}

# 全局缓存变量(全程复用,所有命令自动带入)
CONTAINER_NAME=""
CONTAINER_CID=""
COMPOSE_PROJECT=""
MYSQL_DATA_VOLUME=""
EXIT_CODE=""
OOM_KILLED=""
MEM_LIMIT=""
DIR="full"
LOG_LINES=50
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】0. 本体论分类:容器基础信息采集、全局变量缓存 -----------------------------
section "0. 本体论分类:容器基础信息采集"
# 匹配真实容器名
CONTAINER_NAME=$(docker ps -a --format '{{.Names}}' --filter "name=${CONTAINER_INPUT}" | head -1)
if [[ -z "${CONTAINER_NAME}" ]]; then
    error "容器 ${CONTAINER_INPUT} 不存在,请核对容器名称,当前宿主机所有容器列表:"
    docker ps -a --format "table {{.Names}}\t{{.Status}}"
    exit 1
fi
# 缓存容器CID、compose项目名
CONTAINER_CID=$(docker inspect --format '{{.Id}}' "${CONTAINER_NAME}")
COMPOSE_PROJECT=$(docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "${CONTAINER_NAME}" 2>/dev/null || echo "unknown")
# 自动拼接数据卷路径
MYSQL_DATA_VOLUME="/var/lib/docker/volumes/${COMPOSE_PROJECT}_mysql_data/_data"

# 缓存核心状态参数
STATUS=$(docker inspect --format '{{.State.Status}}' "${CONTAINER_NAME}")
EXIT_CODE=$(docker inspect --format '{{.State.ExitCode}}' "${CONTAINER_NAME}")
OOM_KILLED=$(docker inspect --format '{{.State.OOMKilled}}' "${CONTAINER_NAME}")
STARTED_AT=$(docker inspect --format '{{.State.StartedAt}}' "${CONTAINER_NAME}")
FINISHED_AT=$(docker inspect --format '{{.State.FinishedAt}}' "${CONTAINER_NAME}")
RESTART_COUNT=$(docker inspect --format '{{.RestartCount}}' "${CONTAINER_NAME}")
MEM_LIMIT_RAW=$(docker inspect --format '{{.HostConfig.Memory}}' "${CONTAINER_NAME}")
if [[ "${MEM_LIMIT_RAW}" != "0" ]]; then
    MEM_LIMIT="$(( MEM_LIMIT_RAW / 1024 / 1024 )) MB"
else
    MEM_LIMIT="无内存限制"
fi

# 全部关键信息高亮打印,存入复制清单
print_copy "MySQL容器名称" "${CONTAINER_NAME}"
print_copy "容器完整CID" "${CONTAINER_CID}"
print_copy "Compose项目名" "${COMPOSE_PROJECT}"
print_copy "MySQL数据卷宿主机路径" "${MYSQL_DATA_VOLUME}"
print_copy "容器当前状态" "${STATUS}"
print_copy "容器退出码" "${EXIT_CODE}"
print_copy "是否OOM杀死" "${OOM_KILLED}"
print_copy "容器内存限制" "${MEM_LIMIT}"
print_copy "容器重启累计次数" "${RESTART_COUNT}"
print_copy "诊断报告归档目录" "${DIAG_OUT_DIR}"
print_cmd "cat ${KEY_COPY_FILE} # 一键查看所有可复制ID、路径汇总"

# 故障现象分类判定
if [[ "${STATUS}" == "exited" && "${EXIT_CODE}" -eq 0 ]]; then
    CLASS="正常退出 (手动停止/健康检查主动退出)"
elif [[ "${STATUS}" == "exited" && "${EXIT_CODE}" -eq 137 ]]; then
    CLASS="SIGKILL强制终止 (100%大概率OOM内存耗尽)"
elif [[ "${STATUS}" == "exited" && "${EXIT_CODE}" -ne 0 ]]; then
    CLASS="异常退出,错误码${EXIT_CODE} (启动失败/配置错误/数据损坏/权限不足)"
elif [[ "${STATUS}" == "running" ]]; then
    CLASS="容器运行中但MySQL服务不可达 (死锁/慢查询/端口占用/网络异常)"
else
    CLASS="特殊状态: ${STATUS} (暂停/重启中/被冻结)"
fi
info "故障类别判定: ${CLASS}"

# 保存完整容器元数据快照
docker inspect "${CONTAINER_NAME}" > "${DIAG_OUT_DIR}/container_full_inspect.log"
info "容器完整元数据快照已归档:${DIAG_OUT_DIR}/container_full_inspect.log"

# 手工检验可复制命令
print_cmd "docker inspect --format '{{.RestartCount}}' ${CONTAINER_NAME}"
print_cmd "uptime; dmesg | tail -20"
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】1. 奥卡姆剃刀:极简快速定位根因 -----------------------------
section "1. 奥卡姆剃刀诊断:最少命令锁定故障大类"
info "剃刀第一刀:退出码=${EXIT_CODE}, OOM=${OOM_KILLED}"

# 抓取并归档容器完整日志
docker logs "${CONTAINER_NAME}" > "${DIAG_OUT_DIR}/docker_mysql_full_log.log" 2>&1
# 提取致命错误关键字
ERROR_LOG_FILTER=$(grep -iE "error|fatal|corrupt|full|denied|read.only|space|signal 11|signal 9" "${DIAG_OUT_DIR}/docker_mysql_full_log.log" | tail -${LOG_LINES})
echo -e "\n--- MySQL 错误日志 (末${LOG_LINES}行致命报错) ---"
if [[ -n "${ERROR_LOG_FILTER}" ]]; then
    echo "${ERROR_LOG_FILTER}"
else
    echo "(日志未匹配到崩溃关键字,需人工精读完整日志)"
fi
echo "--------------------------------------------------------"

# 磁盘空间专项快速检测
if echo "${ERROR_LOG_FILTER}" | grep -qi "no space left"; then
    warn "日志检测到磁盘空间耗尽,立即查看宿主机磁盘/Inode占用:"
    df -h /var/lib/docker 2>/dev/null || df -h /
    df -i /var/lib/docker 2>/dev/null || df -i /
fi

# 自动判定后续诊断分支
if [[ "${OOM_KILLED}" == "true" ]]; then
    warn "根因锁定:内存耗尽OOM杀死容器 → 跳转内存深度排查"
    DIR="memory"
elif echo "${ERROR_LOG_FILTER}" | grep -qi "corrupt\|tablespace\|force_recovery"; then
    warn "根因锁定:InnoDB数据文件/表空间损坏 → 跳转数据修复排查"
    DIR="corrupt"
elif echo "${ERROR_LOG_FILTER}" | grep -qi "no space left\|disk full"; then
    warn "根因锁定:磁盘空间/Inode耗尽 → 跳转磁盘资源排查"
    DIR="disk"
else
    info "未命中单一故障特征,执行全栈完整分层诊断"
    DIR="full"
fi

# 日志读取手工命令(容器启动失败时备用)
print_cmd "docker exec -it ${CONTAINER_NAME} cat /var/log/mysql/error.log"
print_cmd "docker run --rm -v ${MYSQL_DATA_VOLUME}:/data alpine cat /data/*.err"
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】2. 资源层深度排查(内存/CPU/磁盘) -----------------------------
# 2.1 内存OOM专项排查
if [[ "${DIR}" == "memory" || "${DIR}" == "full" ]]; then
    section "2.1 内存与OOM深度排查"
    echo "--- 宿主机全局内存/交换分区快照 ---"
    free -h > "${DIAG_OUT_DIR}/host_memory_snapshot.log"
    cat "${DIAG_OUT_DIR}/host_memory_snapshot.log"

    echo -e "\n--- MySQL容器实时资源占用 ---"
    docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}" --filter "name=${CONTAINER_NAME}" || true > "${DIAG_OUT_DIR}/container_resource.log"
    cat "${DIAG_OUT_DIR}/container_resource.log"

    echo -e "\n--- 宿主机内核OOM杀死进程记录 ---"
    dmesg 2>/dev/null | grep -i "killed process" | tail -3 > "${DIAG_OUT_DIR}/host_oom_dmesg.log"
    cat "${DIAG_OUT_DIR}/host_oom_dmesg.log"

    # 可复制优化&排查命令
    print_cmd "docker update --memory 4g ${CONTAINER_NAME} && docker restart ${CONTAINER_NAME}"
    print_cmd "docker exec -it ${CONTAINER_NAME} mysql -uroot -p -e \"SHOW ENGINE INNODB STATUS\G\""
    print_cmd "sed -i 's/innodb_buffer_pool_size=.*/innodb_buffer_pool_size=2G/' docker-compose.yml"
fi

# 2.2 CPU负载排查
if [[ "${DIR}" == "full" ]]; then
    section "2.2 CPU压力负载检查"
    echo "--- 宿主机系统负载 ---"
    uptime > "${DIAG_OUT_DIR}/host_cpu_load.log"
    cat "${DIAG_OUT_DIR}/host_cpu_load.log"
    echo -e "\n--- 容器CPU占用快照 ---"
    docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}" --filter "name=${CONTAINER_NAME}"

    print_cmd "docker exec -it ${CONTAINER_NAME} mysql -uroot -p -e \"SHOW FULL PROCESSLIST;\""
    print_cmd "docker update --cpus 2 ${CONTAINER_NAME}"
fi

# 2.3 磁盘&Inode占用排查
if [[ "${DIR}" == "disk" || "${DIR}" == "full" ]]; then
    section "2.3 磁盘空间、Inode、大文件堆积排查"
    echo "--- 宿主机磁盘分区使用率 ---"
    df -h /var/lib/docker /var/lib/docker/volumes 2>/dev/null || df -h / > "${DIAG_OUT_DIR}/host_disk_df.log"
    cat "${DIAG_OUT_DIR}/host_disk_df.log"

    echo -e "\n--- Inode资源占用(小文件耗尽隐形故障) ---"
    df -i /var/lib/docker /var/lib/docker/volumes 2>/dev/null || df -i / > "${DIAG_OUT_DIR}/host_inode_df.log"
    cat "${DIAG_OUT_DIR}/host_inode_df.log"

    echo -e "\n--- MySQL数据卷Top10大文件(binlog/日志) ---"
    du -sh ${MYSQL_DATA_VOLUME}/* 2>/dev/null | sort -rh | head -10 > "${DIAG_OUT_DIR}/volume_big_files.log"
    cat "${DIAG_OUT_DIR}/volume_big_files.log"

    print_cmd "ls -lh ${MYSQL_DATA_VOLUME}/mysql-bin.*"
    print_cmd "docker exec -it ${CONTAINER_NAME} mysql -uroot -p -e \"PURGE BINARY LOGS BEFORE DATE(NOW() - INTERVAL 3 DAY);\""
fi
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】3. MySQL引擎层:数据损坏、权限、配置 -----------------------------
# 3.1 InnoDB数据损坏恢复方案
if [[ "${DIR}" == "corrupt" || "${DIR}" == "full" ]]; then
    section "3.1 InnoDB表空间损坏 强制恢复流程"
    warn "完整可复制修复步骤(全部预填变量,直接逐条执行)"
    echo "1. 停止容器:docker-compose stop ${CONTAINER_NAME}"
    print_cmd "cp -a ${MYSQL_DATA_VOLUME} /backup/mysql_data_backup_${TIMESTAMP}"
    echo "2. 备份数据卷(务必先备份,防止数据彻底丢失)"
    print_cmd "sed -i '/command:/c\    command: --innodb_force_recovery=1' docker-compose.yml"
    echo "3. 修改compose启动参数,开启强制恢复模式"
    print_cmd "docker-compose up -d ${CONTAINER_NAME}"
    print_cmd "docker exec ${CONTAINER_NAME} mysqldump --all-databases > full_mysql_dump_${TIMESTAMP}.sql"
    echo "4. 启动容器并完整导出全库数据"
    echo "5. 清空损坏数据目录,删除force_recovery参数,重建容器导入备份"
fi

# 3.2 挂载卷权限、SELinux、配置文件检查
if [[ "${DIR}" == "full" ]]; then
    section "3.2 卷权限、SELinux、Compose配置检查"
    echo "--- 数据卷宿主机目录权限(MySQL容器uid默认999) ---"
    if [ -d "${MYSQL_DATA_VOLUME}" ]; then
        ls -ld "${MYSQL_DATA_VOLUME}" > "${DIAG_OUT_DIR}/volume_permission.log"
        cat "${DIAG_OUT_DIR}/volume_permission.log"
    else
        warn "无root权限无法直接读取数据卷目录"
    fi

    # 提取docker-compose配置文件(当前目录)
    if [ -f "./docker-compose.yml" ]; then
        cp ./docker-compose.yml "${DIAG_OUT_DIR}/docker-compose.yml.bak"
        info "已归档当前compose配置文件:${DIAG_OUT_DIR}/docker-compose.yml.bak"
    fi

    print_cmd "chown -R 999:999 ${MYSQL_DATA_VOLUME}"
    print_cmd "chcon -Rt svirt_sandbox_file_t ${MYSQL_DATA_VOLUME}"
    print_cmd "grep -E 'volume|memory|command' docker-compose.yml"
fi
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】4. 宿主机内核、文件系统底层检查 -----------------------------
if [[ "${DIR}" == "full" ]]; then
    section "4. 操作系统内核、文件系统底层校验"
    echo "--- 检查docker存储分区是否只读ro挂载 ---"
    mount | grep -E "docker|overlay" | grep -w "ro" > "${DIAG_OUT_DIR}/ro_mount_check.log"
    cat "${DIAG_OUT_DIR}/ro_mount_check.log"

    echo -e "\n--- 系统时间同步状态(时间漂移会导致binlog/事务异常) ---"
    date "+%Y-%m-%d %H:%M:%S"
    timedatectl status 2>/dev/null | grep -E "synchronized|Local time" || true

    echo -e "\n--- 内核硬件、IO、OOM报错汇总 ---"
    dmesg 2>/dev/null | grep -iE "read.only|io error|oom|killed|disk fail" | tail -5 > "${DIAG_OUT_DIR}/kernel_error.log"
    cat "${DIAG_OUT_DIR}/kernel_error.log"

    print_cmd "sysctl vm.overcommit_memory vm.swappiness fs.file-max"
    print_cmd "mount -o remount,rw /var/lib/docker"
fi
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】5. 整体论聚合全栈快照汇总 -----------------------------
section "5. 整体论聚合视图:全栈信息一览"
echo "========== 容器核心退出快照 =========="
docker inspect --format '
退出码: {{.State.ExitCode}}
OOM杀死: {{.State.OOMKilled}}
启动时间: {{.State.StartedAt}}
停止时间: {{.State.FinishedAt}}
累计重启次数: {{.RestartCount}}
健康检测状态: {{.State.Health.Status}}
容器内存上限: {{.HostConfig.Memory}}
' "${CONTAINER_NAME}" > "${DIAG_OUT_DIR}/final_container_snapshot.log"
cat "${DIAG_OUT_DIR}/final_container_snapshot.log"

echo ""
echo "========== 宿主机核心资源汇总 =========="
grep -E "Mem|Swap" "${DIAG_OUT_DIR}/host_memory_snapshot.log"
tail -1 "${DIAG_OUT_DIR}/host_disk_df.log"
tail -1 "${DIAG_OUT_DIR}/host_inode_df.log"

echo ""
echo "========== 全容器资源占用TOP5 =========="
docker stats --no-stream --format "table {{.Name}}\t{{.MemPerc}}\t{{.CPUPerc}}" --all 2>/dev/null | sort -k2 -rh | head -6 || echo "无权限获取容器统计"

echo ""
echo "========== MySQL关键错误摘要 =========="
echo "${ERROR_LOG_FILTER}"
# -------------------------------------------------------------------------------------------------------------

# -----------------------------【单段可独立运行】6. 双轨恢复方案 + 无为长期预防 -----------------------------
section "6. 双轨快速恢复 + 无为自诊长期优化方案"
echo -e "${GREEN}【紧急恢复轨】一键执行修复命令(匹配当前故障)${NC}"
if [[ "${OOM_KILLED}" == "true" ]]; then
    print_cmd "docker update --memory 2g ${CONTAINER_NAME} && docker restart ${CONTAINER_NAME}"
elif echo "${ERROR_LOG_FILTER}" | grep -qi "no space left"; then
    print_cmd "docker exec -it ${CONTAINER_NAME} mysql -uroot -p -e \"PURGE BINARY LOGS BEFORE NOW()\""
elif echo "${ERROR_LOG_FILTER}" | grep -qi "corrupt"; then
    warn "禁止直接重启!先执行3.1数据备份与强制恢复流程"
else
    print_cmd "docker-compose restart ${CONTAINER_NAME}"
fi

echo -e "\n${GREEN}【复盘分析轨】保留故障现场用于事后排查${NC}"
print_cmd "docker logs ${CONTAINER_NAME} > mysql_crash_log_${TIMESTAMP}.log"
print_cmd "dmesg > host_kernel_log_${TIMESTAMP}.log"
print_cmd "tar -zcvf mysql_diag_archive_${TIMESTAMP}.tar.gz ${DIAG_OUT_DIR}"

echo -e "\n${GREEN}【无为自诊优化】容器长期自愈配置建议${NC}"
echo "1. Compose增加内存限制、CPU限制,杜绝无上限OOM"
echo "2. 配置binlog自动过期,避免日志无限膨胀占满磁盘"
echo "3. 完善healthcheck健康检测,自定义退出码区分故障类型"
echo "4. 日志输出至Loki/ELK,配置磁盘满、OOM告警"
echo "5. 定时全库备份,每月演练数据损坏恢复流程"

# 最终结束提示
echo -e "\n${CYAN}==========================================${NC}"
info "Docker MySQL系统化诊断全部完成"
print_copy "完整诊断归档压缩包命令" "tar -zcvf mysql_diag_archive_${TIMESTAMP}.tar.gz ${DIAG_OUT_DIR}"
warn "故障处理优先级:磁盘空间耗尽 > OOM内存杀死 > InnoDB数据损坏 > 权限/配置错误"
print_cmd "ls ${DIAG_OUT_DIR} # 查看所有诊断快照文件"
echo -e "${CYAN}==========================================${NC}"
相关推荐
小生不才yz1 小时前
Shell脚本精读 · S14-02 | 脚本模板:从 20 行工具到可维护项目
linux
曦尧1 小时前
Bun:面向 JavaScript/TypeScript 的全能超高速一体化工具链
ai·自动化
x-cmd1 小时前
Linus 9 年前说「模糊测试有效」——今天已成 OSS-Fuzz 时代铁律
linux·ai·rust·开源·自动化·agent·安全性测试
流浪0012 小时前
Git篇(三):吃透远程协作:SSH 密钥、分支、标签全流程实战指南
运维·git·ssh
六点_dn2 小时前
Linux学习笔记-chmod命令
linux·笔记·学习
AOwhisky2 小时前
Python 基础语法(上篇 + 下篇)——综合自测题
linux·windows·python
浮江雾3 小时前
Flutter第四节------核心概念学习笔记:从基础语法到异步编程
linux·服务器·开发语言·笔记·flutter·入门·基础
FoldWinCard3 小时前
D4 Linux 进程 内存 等命令
linux·运维·服务器
nuo5342023 小时前
基础 3 —— Docker 的常用命令
docker·容器