MySQL 8.0.32 二进制安装脚本 和初始化 操作系统版本rocky86

以下是一个完整、细致、经过实战检验 的 MySQL 8.0.32 二进制安装脚本。

它包含了我们讨论过的所有改进点:幂等性、本地包优先、socket 等待、详细错误处理、手动修复指南,以及你特别要求的改进版 setup_env 函数(带详细注释)

bash 复制代码
#!/bin/bash
# =============================================================================
# MySQL 8.0.32 二进制部署脚本(幂等 + 教学友好 + 生产稳健)
# 适用于 Rocky Linux 8.6 / CentOS 8+ / RHEL 8+
#
# 功能:
#   - 自动安装依赖、创建用户、解压、初始化、配置 systemd
#   - 支持本地安装包优先(--package 参数)
#   - 自动等待 socket 就绪,避免启动后立即操作失败
#   - 每个关键步骤出错时输出详细手动排查指南
#   - 环境变量配置立即生效(无需用户 source)
#   - 支持重复执行(幂等),支持卸载、重装、状态查看
#
# 用法:./install_mysql.sh [选项]
# =============================================================================

set -euo pipefail

# ---------------------------- 全局变量 ---------------------------------------
SCRIPT_NAME=$(basename "$0")
LOG_FILE="/var/log/mysql_install.log"
MYSQL_VERSION="8.0.32"
MYSQL_TAR="mysql-${MYSQL_VERSION}-linux-glibc2.12-x86_64.tar.xz"
MYSQL_URL="https://cdn.mysql.com/archives/mysql-8.0/${MYSQL_TAR}"
INSTALL_DIR="/usr/local/mysql"
DATA_DIR="/data/mysql"
MYSQL_USER="mysql"
MYSQL_GROUP="mysql"
ROOT_PASSWORD="ChangeMe@123"          # 默认密码,安装后务必修改
SERVICE_NAME="mysqld"
LOCAL_MYSQL_PACKAGE=""                # 用户指定的本地包路径

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

# ---------------------------- 辅助函数 ---------------------------------------
log_info() {
    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
    echo ""
    show_manual_fix "$2"
    exit 1
}

show_manual_fix() {
    local step="$1"
    echo -e "${YELLOW}========== 手动排查步骤 ==========${NC}"
    case "$step" in
        "init")
            echo "1. 查看完整错误日志: cat $DATA_DIR/logs/mysqld.log"
            echo "2. 确认配置文件正确: cat /etc/my.cnf | grep -E 'datadir|basedir|socket'"
            echo "3. 检查数据目录权限: ls -ld $DATA_DIR/data"
            echo "4. 清空数据目录并重新初始化(备份旧数据):"
            echo "   mv $DATA_DIR/data $DATA_DIR/data.bak.\$(date +%Y%m%d%H%M%S)"
            echo "   mkdir -p $DATA_DIR/data && chown mysql:mysql $DATA_DIR/data"
            echo "   $INSTALL_DIR/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql"
            ;;
        "start")
            echo "1. 手动启动测试: $INSTALL_DIR/bin/mysqld_safe --defaults-file=/etc/my.cnf --user=mysql"
            echo "2. 查看错误日志: tail -50 $DATA_DIR/logs/mysqld.log"
            echo "3. 检查端口冲突: netstat -tlnp | grep 3306"
            echo "4. 检查目录权限: ls -ld $DATA_DIR/data $DATA_DIR/logs"
            echo "5. 检查系统资源: df -h; free -h"
            ;;
        "password")
            echo "1. 查看临时密码: grep 'temporary password' $DATA_DIR/logs/mysqld.log"
            echo "2. 手动登录并修改密码:"
            echo "   mysql -uroot -p'临时密码'"
            echo "   ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'; FLUSH PRIVILEGES;"
            echo "3. 如果 socket 问题,使用 TCP 连接: mysql -uroot -p -h127.0.0.1"
            ;;
        "socket")
            echo "1. 检查 MySQL 是否正常运行: ps aux | grep mysqld"
            echo "2. 查看 socket 文件是否存在: ls -l $DATA_DIR/mysql.sock"
            echo "3. 查看配置文件中的 socket 路径: grep socket /etc/my.cnf"
            echo "4. 手动指定 socket 连接: mysql -S $DATA_DIR/mysql.sock -uroot -p"
            ;;
        *)
            echo "请查看日志文件: $LOG_FILE 和 $DATA_DIR/logs/mysqld.log"
            ;;
    esac
}

show_help() {
    cat << EOF
${GREEN}MySQL 8.0.32 二进制部署脚本${NC}
用法: $SCRIPT_NAME [选项]

选项:
  -h, --help          显示此帮助信息
  -p, --password PWD  设置 root 密码(默认为 ChangeMe@123)
  --package PATH      指定本地 MySQL 安装包路径(优先使用)
  --reinstall         强制重新初始化(会备份原数据目录)
  --uninstall         卸载 MySQL(停止服务,删除目录,保留数据备份)
  --status            查看 MySQL 服务状态

示例:
  $SCRIPT_NAME                              # 使用默认密码安装
  $SCRIPT_NAME -p 'MyStrongPwd123!'         # 指定密码
  $SCRIPT_NAME --package /root/mysql-8.0.32.tar.xz   # 使用本地包
  $SCRIPT_NAME --reinstall                  # 重新初始化(保留旧数据备份)
  $SCRIPT_NAME --uninstall                  # 卸载 MySQL
  $SCRIPT_NAME --status                     # 查看状态

注意: 安装完成后请立即修改密码或使用 -p 参数指定强密码。
EOF
}

# ---------------------------- 核心安装函数 -----------------------------------
check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo -e "${RED}[ERROR]${NC} 此脚本必须以 root 用户执行,请使用 sudo 或切换到 root"
        exit 1
    fi
}

check_os() {
    if ! grep -qi "rocky\|centos\|redhat\|almalinux" /etc/os-release; then
        log_warn "当前操作系统可能不是 RHEL 系列,脚本未完全测试,请谨慎使用"
    fi
    local os_version=$(grep "VERSION_ID" /etc/os-release | cut -d '"' -f2 | cut -d '.' -f1)
    if [[ "$os_version" -lt 8 ]]; then
        log_error "需要 RHEL/CentOS 8 及以上版本,当前版本: $os_version" "init"
    fi
}

install_deps() {
    log_info "检查并安装依赖包..."
    local deps=("libaio" "numactl" "ncurses-compat-libs")
    local missing=()
    for dep in "${deps[@]}"; do
        if ! rpm -q "$dep" &>/dev/null; then
            missing+=("$dep")
        fi
    done
    if [[ ${#missing[@]} -gt 0 ]]; then
        log_info "安装缺失依赖: ${missing[*]}"
        dnf install -y "${missing[@]}" >> "$LOG_FILE" 2>&1 || log_error "依赖安装失败" "init"
    else
        log_info "所有依赖已安装"
    fi
}

create_user_group() {
    log_info "创建 mysql 用户和组..."
    if ! getent group "$MYSQL_GROUP" &>/dev/null; then
        groupadd -r "$MYSQL_GROUP"
        log_info "组 $MYSQL_GROUP 已创建"
    fi
    if ! id "$MYSQL_USER" &>/dev/null; then
        useradd -r -g "$MYSQL_GROUP" -s /bin/false "$MYSQL_USER"
        log_info "用户 $MYSQL_USER 已创建"
    else
        log_info "用户 $MYSQL_USER 已存在"
    fi
}

prepare_dirs() {
    log_info "创建所需目录..."
    mkdir -p "$DATA_DIR"/{data,logs,binlog,tmp}
    chown -R "$MYSQL_USER":"$MYSQL_GROUP" "$DATA_DIR"
    chmod 750 "$DATA_DIR"

    if [[ -n "$(ls -A "$DATA_DIR/data" 2>/dev/null)" ]]; then
        log_warn "数据目录 $DATA_DIR/data 不为空,跳过初始化步骤"
        export SKIP_INIT=1
    else
        export SKIP_INIT=0
    fi
}

download_mysql() {
    # 优先使用用户指定的本地包
    if [[ -n "$LOCAL_MYSQL_PACKAGE" ]] && [[ -f "$LOCAL_MYSQL_PACKAGE" ]]; then
        log_info "使用用户指定的本地安装包: $LOCAL_MYSQL_PACKAGE"
        mkdir -p /usr/local/src
        cp "$LOCAL_MYSQL_PACKAGE" /usr/local/src/ 2>/dev/null || ln -s "$LOCAL_MYSQL_PACKAGE" /usr/local/src/
        return 0
    fi

    # 检查常见目录中是否存在安装包
    local search_paths=("/usr/local/src/$MYSQL_TAR" "./$MYSQL_TAR" "/root/$MYSQL_TAR" "/opt/$MYSQL_TAR")
    for path in "${search_paths[@]}"; do
        if [[ -f "$path" ]]; then
            log_info "找到本地安装包: $path"
            mkdir -p /usr/local/src
            cp "$path" /usr/local/src/ 2>/dev/null || true
            return 0
        fi
    done

    log_info "本地未找到安装包,开始下载 MySQL ${MYSQL_VERSION} ..."
    mkdir -p /usr/local/src
    cd /usr/local/src
    wget -q --show-progress "$MYSQL_URL" || log_error "下载失败,请检查网络或手动下载放置到 /usr/local/src/" "init"
    log_info "下载完成"
}

extract_mysql() {
    if [[ -d "$INSTALL_DIR/bin" ]] && [[ -x "$INSTALL_DIR/bin/mysqld" ]]; then
        log_info "MySQL 已安装在 $INSTALL_DIR,跳过解压"
        return 0
    fi
    log_info "解压 MySQL 二进制包到 $INSTALL_DIR ..."
    cd /usr/local/src
    tar -xvf "$MYSQL_TAR" -C /usr/local/ >> "$LOG_FILE" 2>&1 || log_error "解压失败,请检查安装包完整性" "init"
    local extracted_dir="/usr/local/mysql-${MYSQL_VERSION}-linux-glibc2.12-x86_64"
    if [[ -L "$INSTALL_DIR" ]]; then
        rm -f "$INSTALL_DIR"
    elif [[ -e "$INSTALL_DIR" ]]; then
        mv "$INSTALL_DIR" "$INSTALL_DIR.bak.$(date +%Y%m%d%H%M%S)"
    fi
    ln -s "$extracted_dir" "$INSTALL_DIR"
    chown -R root:root "$INSTALL_DIR"
    log_info "解压完成,软链接已创建"
}

# =============================================================================
# 改进版 setup_env 函数(带详细注释)
# 功能:
#   1. 创建独立的 /etc/profile.d/mysql.sh 文件(便于管理)
#   2. 立即导出 PATH 到当前 shell(无需用户 source)
#   3. 同时写入 /etc/profile 作为兜底(兼容老习惯)
#   4. 验证 mysql 命令是否可用
# =============================================================================
setup_env() {
    local profile_file="/etc/profile.d/mysql.sh"

    # ---------- 1. 创建独立的环境变量文件 ----------
    if [[ ! -f "$profile_file" ]]; then
        cat > "$profile_file" <<EOF
# MySQL 环境变量(由安装脚本自动生成)
export PATH=$INSTALL_DIR/bin:\$PATH
EOF
        log_info "已创建环境变量文件: $profile_file"
    else
        if ! grep -q "$INSTALL_DIR/bin" "$profile_file"; then
            echo "export PATH=$INSTALL_DIR/bin:\$PATH" >> "$profile_file"
            log_info "已更新环境变量文件: $profile_file"
        else
            log_info "环境变量文件已存在且包含正确路径,跳过"
        fi
    fi

    # ---------- 2. 当前 shell 立即生效 ----------
    export PATH="$INSTALL_DIR/bin:$PATH"
    log_info "当前会话 PATH 已更新(已加入 $INSTALL_DIR/bin)"

    # ---------- 3. 兜底:同时写入 /etc/profile(避免某些系统不加载 profile.d) ----------
    if ! grep -q "$INSTALL_DIR/bin" /etc/profile 2>/dev/null; then
        echo "" >> /etc/profile
        echo "# MySQL environment (auto-added by install script)" >> /etc/profile
        echo "export PATH=$INSTALL_DIR/bin:\$PATH" >> /etc/profile
        log_info "已追加 MySQL 路径到 /etc/profile(新登录会话也将生效)"
    fi

    # ---------- 4. 验证 mysql 命令是否可用 ----------
    if command -v mysql &>/dev/null; then
        log_info "验证成功: mysql 命令路径 = $(which mysql)"
    else
        log_warn "验证失败: mysql 命令未找到。请手动执行: export PATH=$INSTALL_DIR/bin:\$PATH"
    fi
}

generate_mycnf() {
    local mycnf="/etc/my.cnf"
    if [[ -f "$mycnf" ]]; then
        log_warn "$mycnf 已存在,备份为 ${mycnf}.bak"
        cp "$mycnf" "${mycnf}.bak.$(date +%Y%m%d%H%M%S)"
    fi
    log_info "生成生产级 my.cnf 配置文件..."
    cat > "$mycnf" <<EOF
[client]
port=3306
socket=$DATA_DIR/mysql.sock
default-character-set=utf8mb4

[mysql]
prompt="\\u@\\h [\\d]> "
default-character-set=utf8mb4

[mysqld]
user=$MYSQL_USER
basedir=$INSTALL_DIR
datadir=$DATA_DIR/data
socket=$DATA_DIR/mysql.sock
pid-file=$DATA_DIR/mysqld.pid
port=3306

log-error=$DATA_DIR/logs/mysqld.log
slow-query-log=1
slow-query-log-file=$DATA_DIR/logs/slow.log
long-query-time=2
log-queries-not-using-indexes=1

server-id=1
log-bin=$DATA_DIR/binlog/mysql-bin
binlog-format=ROW
binlog-expire-logs-seconds=604800
max-binlog-size=1G
binlog-cache-size=32M
sync-binlog=1

character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default-time-zone='+08:00'

max-connections=1000
max-connect-errors=100000
connect-timeout=10
wait-timeout=28800
interactive-timeout=28800

thread-cache-size=256
table-open-cache=4096
table-definition-cache=4096

default-storage-engine=INNODB
innodb-buffer-pool-size=5G
innodb_redo_log_capacity=1G
innodb-flush-log-at-trx-commit=1
innodb-file-per-table=1
innodb-open-files=65535
innodb-io-capacity=2000
innodb-flush-method=O_DIRECT
innodb-lock-wait-timeout=50

tmpdir=$DATA_DIR/tmp

sql-mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO"
symbolic-links=0
skip-name-resolve

max-allowed-packet=256M
sort-buffer-size=4M
join-buffer-size=4M
read-buffer-size=2M
read-rnd-buffer-size=8M

performance-schema=1
EOF
    log_info "配置文件已生成: $mycnf"
}

initialize_db() {
    if [[ "$SKIP_INIT" -eq 1 ]]; then
        log_info "数据目录非空,跳过初始化。如需重新初始化请使用 --reinstall 选项"
        return 0
    fi
    log_info "初始化数据库(生成临时密码)..."
    $INSTALL_DIR/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user="$MYSQL_USER" >> "$LOG_FILE" 2>&1
    if [[ $? -ne 0 ]]; then
        log_error "初始化失败,请检查日志" "init"
    fi
    log_info "初始化完成,临时密码请查看日志: $DATA_DIR/logs/mysqld.log"
}

wait_for_socket() {
    local socket_path="$DATA_DIR/mysql.sock"
    local timeout=60
    local waited=0
    log_info "等待 MySQL socket 就绪(最多60秒)..."
    while [[ ! -S "$socket_path" ]] && [[ $waited -lt $timeout ]]; do
        sleep 2
        ((waited+=2))
        echo -n "."
    done
    echo ""
    if [[ ! -S "$socket_path" ]]; then
        log_error "MySQL socket 文件未在 ${timeout} 秒内出现,MySQL 可能启动失败" "socket"
    fi
    log_info "Socket 已就绪: $socket_path"
}

start_mysql() {
    if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
        log_info "MySQL 已在运行(systemd)"
        return 0
    fi
    if pgrep -x "mysqld" > /dev/null; then
        log_info "MySQL 进程已存在,但未通过 systemd 管理"
        return 0
    fi

    if systemctl list-unit-files | grep -q "$SERVICE_NAME.service"; then
        log_info "使用 systemd 启动 MySQL..."
        systemctl start "$SERVICE_NAME" && log_info "MySQL 启动成功" || log_error "systemd 启动失败" "start"
    else
        log_info "使用 mysqld_safe 启动 MySQL..."
        nohup $INSTALL_DIR/bin/mysqld_safe --defaults-file=/etc/my.cnf --user="$MYSQL_USER" > /dev/null 2>&1 &
        sleep 5
        if pgrep -x "mysqld" > /dev/null; then
            log_info "MySQL 启动成功"
        else
            log_error "MySQL 启动失败,请检查日志" "start"
        fi
    fi
    wait_for_socket
}

set_root_password() {
    wait_for_socket   # 再确保一次 socket 已存在

    local temp_pass
    temp_pass=$(grep 'temporary password' "$DATA_DIR/logs/mysqld.log" 2>/dev/null | awk '{print $NF}')
    if [[ -z "$temp_pass" ]]; then
        log_warn "未找到临时密码,可能 root 密码已设置或初始化未完成"
        # 尝试用空密码登录
        if mysql -uroot -h127.0.0.1 -e "exit" &>/dev/null; then
            log_info "root 密码为空,正在设置新密码..."
            mysql -uroot -h127.0.0.1 -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$ROOT_PASSWORD'; FLUSH PRIVILEGES;" && \
                log_info "root 密码已设置" || log_warn "密码设置失败,请手动处理"
        elif mysql -uroot -p"$ROOT_PASSWORD" -h127.0.0.1 -e "exit" &>/dev/null; then
            log_info "root 密码已经是目标密码"
        else
            log_warn "无法自动设置密码,请手动执行以下步骤:"
            show_manual_fix "password"
        fi
        return 0
    fi

    log_info "使用临时密码设置 root 密码..."
    if $INSTALL_DIR/bin/mysql -uroot -p"$temp_pass" -h127.0.0.1 --connect-expired-password <<EOF >/dev/null 2>&1
ALTER USER 'root'@'localhost' IDENTIFIED BY '$ROOT_PASSWORD';
FLUSH PRIVILEGES;
EOF
    then
        log_info "root 密码已成功修改"
    else
        log_error "密码修改失败,请手动执行" "password"
    fi
}

setup_systemd() {
    local service_file="/etc/systemd/system/${SERVICE_NAME}.service"
    if [[ -f "$service_file" ]]; then
        log_info "systemd 服务文件已存在,跳过创建"
    else
        cat > "$service_file" <<EOF
[Unit]
Description=MySQL Server
After=network.target

[Service]
User=$MYSQL_USER
Group=$MYSQL_GROUP
Type=simple
ExecStart=$INSTALL_DIR/bin/mysqld --defaults-file=/etc/my.cnf
ExecStop=$INSTALL_DIR/bin/mysqladmin --defaults-file=/etc/my.cnf shutdown
Restart=on-failure
RestartSec=10
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF
        systemctl daemon-reload
        log_info "systemd 服务文件已创建"
    fi

    if systemctl is-enabled "$SERVICE_NAME" &>/dev/null; then
        log_info "MySQL 已设置为开机自启"
    else
        systemctl enable "$SERVICE_NAME" && log_info "已启用开机自启"
    fi
}

setup_logrotate() {
    local logrotate_file="/etc/logrotate.d/mysql"
    if [[ -f "$logrotate_file" ]]; then
        log_info "logrotate 配置已存在,跳过"
        return 0
    fi
    cat > "$logrotate_file" <<EOF
$DATA_DIR/logs/*.log {
    daily
    rotate 7
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        if [ -f $DATA_DIR/mysqld.pid ]; then
            kill -USR1 \`cat $DATA_DIR/mysqld.pid\`
        fi
    endscript
}
EOF
    log_info "logrotate 配置已添加"
}

secure_installation() {
    log_info "执行安全加固(删除匿名用户、测试库)..."
    $INSTALL_DIR/bin/mysql -uroot -p"$ROOT_PASSWORD" -h127.0.0.1 <<EOF 2>/dev/null
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE authentication_string='';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test';
FLUSH PRIVILEGES;
EOF
    if [[ $? -eq 0 ]]; then
        log_info "安全加固完成"
    else
        log_warn "安全加固失败,请手动执行相关 SQL"
    fi
}

show_summary() {
    echo ""
    echo "============================================================"
    echo -e "${GREEN}MySQL 8.0.32 安装完成!${NC}"
    echo "============================================================"
    echo -e "安装路径: ${YELLOW}$INSTALL_DIR${NC}"
    echo -e "数据目录: ${YELLOW}$DATA_DIR/data${NC}"
    echo -e "日志目录: ${YELLOW}$DATA_DIR/logs${NC}"
    echo -e "配置文件: ${YELLOW}/etc/my.cnf${NC}"
    echo -e "root 密码: ${YELLOW}$ROOT_PASSWORD${NC}"
    echo -e "启动命令: ${YELLOW}systemctl start $SERVICE_NAME${NC}"
    echo -e "停止命令: ${YELLOW}systemctl stop $SERVICE_NAME${NC}"
    echo -e "查看状态: ${YELLOW}systemctl status $SERVICE_NAME${NC}"
    echo -e "登录测试: ${YELLOW}mysql -uroot -p${NC}"
    echo "============================================================"
    echo -e "${RED}请务必修改 root 密码(如果使用默认密码)!${NC}"
    echo "============================================================"
}

# ---------------------------- 运维功能 -----------------------------------
uninstall_mysql() {
    log_warn "即将卸载 MySQL,此操作不可逆!"
    read -p "是否继续?(yes/no): " confirm
    if [[ "$confirm" != "yes" ]]; then
        log_info "卸载已取消"
        exit 0
    fi
    log_info "停止 MySQL 服务..."
    systemctl stop "$SERVICE_NAME" 2>/dev/null || true
    systemctl disable "$SERVICE_NAME" 2>/dev/null || true
    pkill -9 mysqld 2>/dev/null || true
    log_info "删除 systemd 服务文件..."
    rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
    systemctl daemon-reload
    log_info "删除安装目录..."
    rm -rf "$INSTALL_DIR" /usr/local/mysql-*
    log_info "备份数据目录(重命名)..."
    mv "$DATA_DIR" "${DATA_DIR}.uninstalled.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
    log_info "删除配置文件..."
    rm -f /etc/my.cnf /etc/my.cnf.bak*
    log_info "删除环境变量..."
    rm -f /etc/profile.d/mysql.sh
    sed -i '/\/usr\/local\/mysql\/bin/d' /etc/profile 2>/dev/null || true
    log_info "删除 logrotate 配置..."
    rm -f /etc/logrotate.d/mysql
    log_info "MySQL 已卸载。原数据目录已重命名为 ${DATA_DIR}.uninstalled.*"
}

show_status() {
    if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
        echo -e "${GREEN}MySQL 服务状态: 运行中${NC}"
        systemctl status "$SERVICE_NAME" --no-pager
    elif pgrep -x "mysqld" > /dev/null; then
        echo -e "${GREEN}MySQL 进程存在但未使用 systemd 管理${NC}"
        pgrep -a mysqld
    else
        echo -e "${RED}MySQL 未运行${NC}"
    fi
}

reinitialize() {
    log_warn "重新初始化数据库,当前数据目录将被备份"
    read -p "确认继续?(yes/no): " confirm
    if [[ "$confirm" != "yes" ]]; then
        log_info "操作取消"
        exit 0
    fi
    systemctl stop "$SERVICE_NAME" 2>/dev/null || pkill mysqld || true
    local backup_dir="${DATA_DIR}.backup.$(date +%Y%m%d%H%M%S)"
    mv "$DATA_DIR" "$backup_dir"
    log_info "原数据目录已备份到 $backup_dir"
    mkdir -p "$DATA_DIR"/{data,logs,binlog,tmp}
    chown -R "$MYSQL_USER":"$MYSQL_GROUP" "$DATA_DIR"
    chmod 750 "$DATA_DIR"
    export SKIP_INIT=0
    initialize_db
    start_mysql
    set_root_password
    secure_installation
    log_info "重新初始化完成,新 root 密码: $ROOT_PASSWORD"
}

# ---------------------------- 主流程 ---------------------------------------
main() {
    ACTION="install"
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                show_help
                exit 0
                ;;
            -p|--password)
                ROOT_PASSWORD="$2"
                shift 2
                ;;
            --package)
                LOCAL_MYSQL_PACKAGE="$2"
                shift 2
                ;;
            --reinstall)
                ACTION="reinstall"
                shift
                ;;
            --uninstall)
                ACTION="uninstall"
                shift
                ;;
            --status)
                ACTION="status"
                shift
                ;;
            *)
                echo -e "${RED}[ERROR]${NC} 未知选项: $1,使用 -h 查看帮助"
                exit 1
                ;;
        esac
    done

    case "$ACTION" in
        install)
            check_root
            check_os
            install_deps
            create_user_group
            prepare_dirs
            download_mysql
            extract_mysql
            setup_env          # 关键:环境变量配置并立即生效
            generate_mycnf
            initialize_db
            setup_systemd
            start_mysql
            set_root_password
            setup_logrotate
            secure_installation
            show_summary
            ;;
        reinstall)
            check_root
            reinitialize
            ;;
        uninstall)
            check_root
            uninstall_mysql
            ;;
        status)
            show_status
            ;;
    esac
}

main "$@"

脚本亮点总结

模块 功能
setup_env 同时写入 /etc/profile.d/mysql.sh/etc/profile,并立即 export PATH用户无需 source
本地包优先 检查用户指定路径、/usr/local/src/root、当前目录,找不到才下载
Socket 等待 启动后最多等待 60 秒,确保 socket 文件生成后再设置密码
错误处理 关键步骤失败时调用 show_manual_fix,输出详细手动排查步骤
幂等性 用户/组、目录、环境变量、systemd 服务等均做存在性检查,可重复执行
运维命令 支持 --reinstall--uninstall--status,数据目录自动备份
日志记录 所有输出同时写入 /var/log/mysql_install.log,便于排错

使用方法

bash 复制代码
# 1. 保存脚本
vim install_mysql.sh

# 2. 添加执行权限
chmod +x install_mysql.sh

# 3. 安装(使用默认密码 ChangeMe@123)
./install_mysql.sh

# 4. 指定密码安装
./install_mysql.sh -p 'Xian@9676'

# 5. 使用本地包安装
./install_mysql.sh --package /root/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz -p 'Xian@9676'

# 6. 查看状态
./install_mysql.sh --status

# 7. 重新初始化(备份数据后重装)
./install_mysql.sh --reinstall

# 8. 卸载
./install_mysql.sh --uninstall

好的,以下是针对我们刚才几个脚本常见报错及处理方案的汇总文档,方便学生遇到问题时快速定位解决。


MySQL 8.0 安装脚本常见报错及处理手册

一、依赖与环境问题

1.1 chown: invalid user: 'mysql:mysql'

现象 :执行 chown -R mysql:mysql /data/mysql 时报错

原因:没有先创建 mysql 用户

解决

bash 复制代码
groupadd mysql
useradd -r -g mysql -s /bin/false mysql
# 然后再执行 chown

1.2 mysql: command not found

现象 :执行 mysql -uroot -p 提示找不到命令

原因:环境变量未配置或未生效

解决

bash 复制代码
# 临时生效(当前终端)
export PATH=/usr/local/mysql/bin:$PATH

# 永久生效
echo 'export PATH=/usr/local/mysql/bin:$PATH' > /etc/profile.d/mysql.sh
source /etc/profile.d/mysql.sh

1.3 error while loading shared libraries: libtinfo.so.5

现象 :执行 mysql 命令时报缺少共享库

原因 :缺少 ncurses-compat-libs 依赖包

解决

bash 复制代码
dnf install -y ncurses-compat-libs

1.4 libaio.so.1: cannot open shared object file

现象:mysqld 启动失败,提示缺少 libaio

原因 :缺少 libaio 依赖包

解决

bash 复制代码
dnf install -y libaio

二、初始化问题

2.1 unknown variable 'query-cache-type=0'

现象:初始化时报未知变量错误

原因:MySQL 8.0 已移除 Query Cache 功能

解决 :删除 /etc/my.cnf 中的以下两行

ini 复制代码
# query-cache-type=0
# query-cache-size=0

2.2 [ERROR] [MY-011011] Failed to find valid data directory

现象:启动时找不到有效的数据目录

原因:数据目录不存在、为空、权限不对,或从未初始化

解决

bash 复制代码
# 1. 确保目录存在且权限正确
mkdir -p /data/mysql/data
chown -R mysql:mysql /data/mysql

# 2. 执行初始化
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql

2.3 --initialize specified but the data directory has files in it

现象:初始化时报数据目录已有文件

原因:之前初始化失败残留了文件,或目录非空

解决

bash 复制代码
# 1. 备份原数据目录
mv /data/mysql/data /data/mysql/data.bak.$(date +%Y%m%d%H%M%S)

# 2. 创建新的空目录
mkdir -p /data/mysql/data
chown -R mysql:mysql /data/mysql

# 3. 重新初始化
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql

2.4 grep 'temporary password' 找不到任何输出

现象:查看日志时没有临时密码

原因:初始化失败,或日志路径配置错误

解决

bash 复制代码
# 1. 查看完整错误日志
cat /data/mysql/logs/mysqld.log

# 2. 检查配置文件中的 log-error 路径
grep log-error /etc/my.cnf

# 3. 重新初始化(先清空数据目录)
rm -rf /data/mysql/data/*
/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=mysql

三、启动问题

3.1 systemctl start mysqld 卡住不动

现象 :执行 systemctl start mysqld 后命令一直不返回

原因 :使用了 Type=forking 配合 mysqld_safe,启动等待时间过长

解决 :改用 Type=simple 直接调用 mysqld

ini 复制代码
[Service]
Type=simple
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf

3.2 Can't connect to local MySQL server through socket

现象:登录时报 socket 连接失败

原因:MySQL 未启动,或 socket 路径不匹配

解决

bash 复制代码
# 1. 检查 MySQL 是否运行
systemctl status mysqld

# 2. 检查 socket 文件是否存在
ls -l /data/mysql/mysql.sock

# 3. 手动指定 socket 路径连接
mysql -S /data/mysql/mysql.sock -uroot -p

3.3 Killed 进程被杀死

现象:启动或清理时进程被 Killed

原因:内存不足,或 OOM Killer 触发

解决

bash 复制代码
# 1. 检查系统内存
free -h

# 2. 检查是否有其他 mysql 进程残留
ps aux | grep mysql

# 3. 强制杀死残留进程后再启动
pkill -9 mysqld
systemctl start mysqld

四、连接与权限问题

4.1 Access denied for user 'root'@'localhost'

现象:登录时报密码错误

原因:密码输入错误,或使用了临时密码但未修改

解决

bash 复制代码
# 方案一:重新查看临时密码
grep 'temporary password' /data/mysql/logs/mysqld.log

# 方案二:跳过权限表重置密码(见下方 4.2)

4.2 ERROR 1820 (HY000): You must reset your password

现象:使用临时密码登录后无法执行任何命令

原因:临时密码过期,必须先修改密码

解决

sql 复制代码
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
FLUSH PRIVILEGES;

4.3 Unknown system variable 'validate_password.policy'

现象:设置密码策略时报未知变量

原因:密码验证插件未加载

解决

sql 复制代码
-- 方法一:跳过策略直接修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';

-- 方法二:加载插件后再设置策略
INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 4;

4.4 1251 - Client does not support authentication protocol

现象:Navicat 等老客户端连接时报错

原因 :MySQL 8.0 默认使用 caching_sha2_password,老客户端不支持

解决

sql 复制代码
ALTER USER '用户名'@'%' IDENTIFIED WITH mysql_native_password BY '密码';
FLUSH PRIVILEGES;

五、脚本执行问题

5.1 清理脚本执行到一半就停了

现象 :执行 clean-mysql.sh 时显示 Killed 后退出

原因set -e + pkill 返回非 0,导致脚本提前退出

解决 :在可能失败的命令后加 || true

bash 复制代码
pkill -9 mysqld 2>/dev/null || true
systemctl stop mysqld 2>/dev/null || true

5.2 安装脚本重复执行时报错

现象:第二次运行脚本时某些步骤失败

原因:脚本未做幂等性处理

解决:使用我们提供的增强版脚本(已包含幂等性判断),关键函数如下:

  • create_user_group:判断用户/组是否已存在
  • prepare_dirs:判断数据目录是否为空
  • setup_env:判断环境变量文件是否已存在
  • setup_systemd:判断服务文件是否已存在

5.3 wget: command not found

现象:脚本下载时提示找不到 wget

原因:系统未安装 wget

解决

bash 复制代码
dnf install -y wget

六、快速排错命令汇总

场景 命令
查看 MySQL 错误日志 tail -50 /data/mysql/logs/mysqld.log
查看 MySQL 是否运行 systemctl status mysqld
查看端口监听 `netstat -tlnp
查看环境变量 `echo $PATH
查看安装目录 ls -la /usr/local/mysql
查看数据目录 ls -la /data/mysql/data
查看配置文件 `cat /etc/my.cnf
手动测试启动 /usr/local/mysql/bin/mysqld_safe --defaults-file=/etc/my.cnf --user=mysql

七、安装成功后验证清单

bash 复制代码
# 1. 服务状态
systemctl status mysqld

# 2. 端口监听
ss -tlnp | grep 3306

# 3. 环境变量
mysql -V

# 4. 登录测试
mysql -uroot -p -e "SELECT VERSION();"

# 5. 查看数据库
mysql -uroot -p -e "SHOW DATABASES;"

# 6. 查看用户
mysql -uroot -p -e "SELECT User, Host, plugin FROM mysql.user;"

好的,以下是创建用户Navicat 连接配置的完整命令和说明:


一、创建 MySQL 用户

1.1 登录 MySQL

bash 复制代码
mysql -uroot -p

1.2 降低密码策略(可选,仅测试环境)

sql 复制代码
SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 4;

1.3 创建用户并授权

sql 复制代码
-- 创建本地用户(仅本机可连)
CREATE USER 'admin'@'localhost' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;

-- 创建远程用户(任意 IP 可连)
CREATE USER 'admin'@'%' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;

-- 创建指定 IP 用户(仅特定 IP 可连)
CREATE USER 'admin'@'192.168.1.%' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'192.168.1.%' WITH GRANT OPTION;

-- 刷新权限
FLUSH PRIVILEGES;

1.4 查看已创建的用户

sql 复制代码
SELECT User, Host, plugin FROM mysql.user;

1.5 删除用户

sql 复制代码
DROP USER 'admin'@'%';
FLUSH PRIVILEGES;

1.6 修改用户密码

sql 复制代码
ALTER USER 'admin'@'%' IDENTIFIED BY '新密码';
FLUSH PRIVILEGES;

二、Navicat 连接 MySQL 8.0 配置

2.1 报错现象

复制代码
1251 - Client does not support authentication protocol requested by server; consider upgrading MySQL client

2.2 原因

MySQL 8.0 默认使用 caching_sha2_password 认证插件,老版本 Navicat 不支持。

2.3 解决方案(三选一)

方案一:修改用户认证插件(推荐)
sql 复制代码
-- 修改已存在的用户
ALTER USER 'admin'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
FLUSH PRIVILEGES;
方案二:创建用户时指定插件
sql 复制代码
CREATE USER 'admin'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
方案三:修改 MySQL 全局默认认证插件

编辑 /etc/my.cnf,在 [mysqld] 段添加:

ini 复制代码
default_authentication_plugin=mysql_native_password

然后重启 MySQL:

bash 复制代码
systemctl restart mysqld

2.4 验证认证插件

sql 复制代码
SELECT User, Host, plugin FROM mysql.user WHERE User='admin';

输出应显示 mysql_native_password


三、防火墙配置(允许远程连接)

bash 复制代码
# 开放 3306 端口
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload

# 验证
firewall-cmd --list-ports

四、Navicat 连接配置步骤

  1. 打开 Navicat,点击 连接MySQL
  2. 填写以下信息:
    • 连接名 :自定义(如 MySQL8
    • 主机:服务器 IP 地址
    • 端口3306
    • 用户名admin
    • 密码123456
  3. 点击 测试连接
  4. 成功后会显示 "连接成功"

五、完整一键脚本(复制粘贴执行)

登录 MySQL 后执行以下完整命令:

sql 复制代码
-- 1. 降低密码策略(可选)
SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 4;

-- 2. 创建 admin 用户(兼容 Navicat)
CREATE USER IF NOT EXISTS 'admin'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;

-- 3. 刷新权限
FLUSH PRIVILEGES;

-- 4. 验证
SELECT User, Host, plugin FROM mysql.user WHERE User='admin';

六、常见问题

问题 原因 解决
连接超时 防火墙未开放端口 执行防火墙开放命令
Access denied 密码错误 确认密码是否正确
Host 'xxx' is not allowed to connect 用户未授权该 IP 使用 'admin'@'%' 或指定 IP 段
认证协议错误 插件不兼容 执行方案一修改插件

相关推荐
Trouvaille ~1 小时前
【Redis篇】Redis 主从复制:数据同步的原理与实现
数据库·redis·缓存·中间件·高可用·主从复制·后端开发
李白的天不白1 小时前
mysql 版本错误导致读取格式错乱
adb
真实的菜1 小时前
Redis 从入门到精通(五):哨兵模式(Sentinel)—— 自动故障转移的完整原理与实战
数据库·redis·sentinel
唔661 小时前
(二)补充完整的数据库、中间件、MQTT、JAR后台和Web前端的部署脚本,全部一键自动化。
数据库·中间件·jar
六月雨滴1 小时前
Oracle 内存优化
数据库·oracle
学代码的真由酱2 小时前
MySQL数据库进阶-数据库设计实践-Java
数据库·mysql·数据库设计
遇事不決洛必達2 小时前
【数据库系列】本地映射云服务器Mysql的方法
服务器·数据库·mysql·定时任务
海鸥-w2 小时前
用python (fastapi)做项目第一天创建项目结构,数据建表,ORM配置安装,写第一个接口
数据库·python·fastapi
zfoo-framework2 小时前
通过redis-cli+lua脚本查询redis数据
数据库·redis·lua