将以下脚本保存为 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}"