以下是一个完整、细致、经过实战检验 的 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 连接配置步骤
- 打开 Navicat,点击 连接 → MySQL
- 填写以下信息:
- 连接名 :自定义(如
MySQL8) - 主机:服务器 IP 地址
- 端口 :
3306 - 用户名 :
admin - 密码 :
123456
- 连接名 :自定义(如
- 点击 测试连接
- 成功后会显示 "连接成功"
五、完整一键脚本(复制粘贴执行)
登录 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 段 |
| 认证协议错误 | 插件不兼容 | 执行方案一修改插件 |