分享一个MySQL/MariaDB/Percona全版本root密码重置工具

直接上脚本了

!/usr/bin/env bash

============================================================

MySQL / MariaDB / Percona 全版本 root 密码重置工具

用法:bash reset_all.sh --help

============================================================

set -uo pipefail
SCRIPT_NAME=(basename "0")

SCRIPT_VERSION="2.0.0"

---------- 帮助 ----------

usage(){

cat <<EOF

{SCRIPT_NAME} {SCRIPT_VERSION} --- MySQL / MariaDB / Percona root 密码重置
用法:

bash ${SCRIPT_NAME} 选项 <新密码> my.cnf路径
选项:

-h, --help 显示本帮助并退出(不需要 root,不操作数据库)

-V, --version 显示脚本版本并退出

-c, --config <路径> 指定 my.cnf 路径;不指定则自动探测

--password <密码> 指定新密码(等价于位置参数,注意会出现在 ps 输出中)

--auth-plugin <插件> 仅 MySQL 5.7.6 ~ 8.3 生效,指定认证插件

例如 mysql_native_password、caching_sha2_password

--keep-network 临时阶段保留 TCP 监听(默认使用 skip-networking 关闭)

-- 结束选项解析,其后全部按位置参数处理
环境变量:

NO_SKIP_NETWORKING=1 等价于 --keep-network

AUTH_PLUGIN=<插件名> 等价于 --auth-plugin

MYSQL_NEW_PASS=<密码> 通过环境变量传入新密码,避免出现在命令历史中
版本兼容:

MySQL 4.1 ~ 5.6 SET PASSWORD ... = PASSWORD(),权限表字段 Password

MySQL 5.7.0 ~ 5.7.5 SET PASSWORD ... = PASSWORD(),权限表字段 Password

MySQL 5.7.6 ~ 5.7.x ALTER USER ... IDENTIFIED BY,字段 authentication_string

MySQL 8.0 ~ 8.3 ALTER USER ... IDENTIFIED BY,默认 caching_sha2_password

MySQL 8.4 ~ 9.x ALTER USER ... IDENTIFIED BY,mysql_native_password 已移除

MariaDB 5.5 ~ 10.1 SET PASSWORD ... = PASSWORD(),字段 Password

MariaDB 10.2 ~ 10.3 ALTER USER ... IDENTIFIED BY,字段 Password

MariaDB 10.4 ~ 11.x ALTER USER / SET PASSWORD,mysql.user 已为视图
执行前提:

  1. 必须以操作系统 root 执行(--help / --version 除外)
  2. 必须处于维护窗口,脚本会停止并重启数据库实例两次
  3. 建议先完成数据与配置备份
  4. 实例若处于复制链路,改密会写入 binlog 并向下游传播
    工作原理:

停止实例 → 以 skip-grant-tables 启动 → 在同一个连接内先 FLUSH PRIVILEGES

再执行改密语句 → 还原配置并正常重启 → 用新密码验证登录
注意:FLUSH PRIVILEGES 会重新加载 ACL 并恢复密码校验,因此改密语句

必须与该命令在同一连接内完成;分连接执行会导致后续连接返回 ERROR 1045。
退出码:

0 改密成功且新密码验证通过

1 执行失败(连接失败、改密失败、验证失败等)

2 参数用法错误
示例:

查看帮助

bash ${SCRIPT_NAME} --help
最简用法(自动探测配置、socket、端口)

bash ${SCRIPT_NAME} 'MyN3wP@ss'
指定配置文件

bash ${SCRIPT_NAME} -c /etc/my.cnf 'MyN3wP@ss'
MySQL 8.0 需要兼容老客户端

bash ${SCRIPT_NAME} --auth-plugin mysql_native_password 'MyN3wP@ss'
临时阶段保留网络监听

bash ${SCRIPT_NAME} --keep-network 'MyN3wP@ss'
通过环境变量传密码(推荐,避免进入命令历史)

MYSQL_NEW_PASS='MyN3wP@ss' bash $
安全提示:

使用 --password 或位置参数时,密码会出现在 ps 与 shell 历史中;

生产环境建议改用 MYSQL_NEW_PASS 环境变量,并在执行后清理历史。

EOF

}
err(){ echo "错误 *" \>\&2; } info(){ echo "\[信息\] * "; }

warn()

---------- 参数解析 ----------

NEW_PASS="${MYSQL_NEW_PASS:-}"

CONF=""

AUTH_PLUGIN="${AUTH_PLUGIN:-}"

KEEP_NET=0
while $# -gt 0 ; do

case "$1" in

-h|--help) usage; exit 0 ;;

-V|--version) echo "{SCRIPT_NAME} {SCRIPT_VERSION}"; exit 0 ;;

-c|--config)

$# -ge 2 || { err "选项 $1 需要参数"; exit 2; }

CONF="$2"; shift 2 ;;

--config=) CONF="${1# =}"; shift ;;

--password)

$# -ge 2 || { err "选项 $1 需要参数"; exit 2; }

NEW_PASS="$2"; shift 2 ;;

--password=) NEW_PASS="${1# =}"; shift ;;

--auth-plugin)

$# -ge 2 || { err "选项 $1 需要参数"; exit 2; }

AUTH_PLUGIN="$2"; shift 2 ;;

--auth-plugin=) AUTH_PLUGIN="${1# =}"; shift ;;

--keep-network) KEEP_NET=1; shift ;;

--) shift; break ;;

-*) err "未知选项:1"; echo "使用 {SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;

*)

if -z "$NEW_PASS" ; then NEW_PASS="$1"

elif -z "$CONF" ; then CONF="$1"

else err "多余的参数:1"; echo "使用 {SCRIPT_NAME} --help 查看帮助" >&2; exit 2

fi

shift ;;

esac

done
while $# -gt 0 ; do

if -z "$NEW_PASS" ; then NEW_PASS="$1"

elif -z "$CONF" ; then CONF="$1"

else err "多余的参数:$1"; exit 2

fi

shift

done
if -z "$NEW_PASS" ; then

err "缺少新密码参数"

echo "用法:bash ${SCRIPT_NAME} 选项 <新密码> my.cnf路径" >&2

echo "帮助:bash ${SCRIPT_NAME} --help" >&2

exit 2

fi
"$(id -u)" -ne 0 &&
case "$NEW_PASS" in

"'" |"\" ) err "密码不能包含单引号或反斜杠"; exit 2 ;;

esac

---------- 1. 定位配置文件 ----------

if -z "$CONF" ; then

for f in /etc/my.cnf /etc/mysql/my.cnf /etc/mysql/mysql.conf.d/mysqld.cnf

/usr/local/mysql/etc/my.cnf /usr/local/mysql/my.cnf; do

-f "$f" && CONF="$f" && break

done

fi

if -z "$CONF" || ! -f "$CONF" ; then

err "未找到配置文件,请用 -c 指定"

exit 1

fi

info "配置文件:$CONF"

---------- 2. 定位 mysql 客户端 ----------

MYSQL_BIN=""

for p in "$(command -v mysql 2>/dev/null)"

/usr/local/mysql/bin/mysql /usr/bin/mysql /usr/local/bin/mysql; do

-n "$p" && -x "$p" && MYSQL_BIN="$p" && break

done

-z "$MYSQL_BIN" && { err "未找到 mysql 客户端"; exit 1; }

info "客户端:$MYSQL_BIN"

---------- 3. 推导 socket / port 候选 ----------

SOCK_CANDS=""; PORT_CANDS=""

MPD=$(command -v my_print_defaults 2>/dev/null || true)

if -n "$MPD" ; then

DEFAULTS=("MPD" --defaults-file="$CONF" mysqld client 2>/dev/null || true)

s=(printf '%s\\n' "DEFAULTS" | awk -F= '/^--socket=/{v=$2} END{print v}')

p=(printf '%s\\n' "DEFAULTS" | awk -F= '/^--port=/{v=$2} END{print v}')

-n "$s" && SOCK_CANDS="$s"

-n "$p" && PORT_CANDS="$p"

fi

for s in /tmp/mysql.sock /var/lib/mysql/mysql.sock /run/mysqld/mysqld.sock

/var/run/mysqld/mysqld.sock /usr/local/mysql/data/mysql.sock; do

SOCK_CANDS="SOCK_CANDS s"

done

s=$(ps -ef 2>/dev/null | grep -oE -- '--socket=\^ +' | head -1 | cut -d= -f2)

-n "$s" && SOCK_CANDS="s SOCK_CANDS"

for p in 3306 23306 3307 3308; do PORT_CANDS="PORT_CANDS p"; done

---------- 4. 识别服务管理方式 ----------

SVC_KIND=""; SVC_NAME=""

if command -v systemctl >/dev/null 2>&1; then

for s in mysqld mysql mariadb; do

if systemctl list-unit-files 2>/dev/null | grep -q "^${s}.service"; then

SVC_KIND="systemd"; SVC_NAME="$s"; break

fi

done

fi

if -z "$SVC_KIND" ; then

for s in mysql mysqld mariadb; do

if -x "/etc/init.d/$s" ; then

SVC_KIND="sysv"; SVC_NAME="$s"; break

fi

done

fi

-z "$SVC_KIND" && SVC_KIND="mysqld_safe"

info "服务管理:SVC_KIND {SVC_NAME:-无}"

---------- 5. 连接探测与服务控制 ----------

CONN_OPTS=""; CONN_DESC=""

sql()
probe(){

local s p

for s in $SOCK_CANDS; do

-S "$s" || continue

if "MYSQL_BIN" --socket="s" -uroot --connect-timeout=5

-e "SELECT 1" >/dev/null 2>&1; then

CONN_OPTS="--socket=s"; CONN_DESC="socket=s"; return 0

fi

done

for p in $PORT_CANDS; do

if "MYSQL_BIN" -h127.0.0.1 -P"p" -uroot --connect-timeout=5

-e "SELECT 1" >/dev/null 2>&1; then

CONN_OPTS="-h127.0.0.1 -Pp"; CONN_DESC="tcp=127.0.0.1:p"; return 0

fi

done

return 1

}
wait_ready(){

local i

for i in $(seq 1 60); do probe && return 0; sleep 2; done

return 1

}
start_normal(){

case "$SVC_KIND" in

systemd) systemctl start "$SVC_NAME" ;;

sysv) "/etc/init.d/$SVC_NAME" start ;;

*)

local ms

ms=$(command -v mysqld_safe 2>/dev/null || echo /usr/local/mysql/bin/mysqld_safe)

nohup "$ms" >/tmp/mysql_reset_safe.log 2>&1 &

;;

esac

}
start_skip(){

case "$SVC_KIND" in

systemd) systemctl start "$SVC_NAME" ;;

sysv) "/etc/init.d/$SVC_NAME" start ;;

*)

local ms

ms=$(command -v mysqld_safe 2>/dev/null || echo /usr/local/mysql/bin/mysqld_safe)

nohup "ms" --skip-grant-tables SKIP_NET >/tmp/mysql_reset_safe.log 2>&1 &

;;

esac

}
stop_db(){

case "$SVC_KIND" in

systemd) systemctl stop "$SVC_NAME" ;;

sysv) "/etc/init.d/$SVC_NAME" stop ;;

*)

sql -e "SHUTDOWN;" >/dev/null 2>&1

sleep 3

if command -v pkill >/dev/null 2>&1; then

pkill -f mysqld_safe >/dev/null 2>&1

pkill -f 'mysqld ' >/dev/null 2>&1

fi

sleep 2

;;

esac

}

---------- 6. 备份与回滚 ----------

BK="{CONF}.bak.(date +%Y%m%d%H%M%S)"

cp -a "CONF" "BK" || { err "备份配置失败"; exit 1; }

info "配置已备份:$BK"
RESTORED=0

restore_cfg(){

"$RESTORED" -eq 1 && return 0

RESTORED=1

info "停止实例并还原配置..."

stop_db

sleep 2

cp -a "BK" "CONF"

start_normal

info "配置已还原:$CONF"

}

trap 'restore_cfg; exit 1' INT TERM

---------- 7. 进入跳过权限校验模式 ----------

SKIP_NET=""

"$KEEP_NET" -ne 1 && "${NO_SKIP_NETWORKING:-0}" != "1" && SKIP_NET="--skip-networking"
info "停止数据库..."

stop_db

sleep 3
if "$SVC_KIND" != "mysqld_safe" ; then

{

echo ""

echo "mysqld"

echo "skip-grant-tables"

-n "$SKIP_NET" && echo "skip-networking"

} >> "$CONF"

fi
info "以跳过权限校验方式启动..."

start_skip
if ! wait_ready; then

err "120 秒内无法连接,请查看错误日志"

restore_cfg

exit 1

fi

info "已连接:$CONN_DESC"

---------- 8. 识别版本与分支 ----------

VER_RAW=$(sql -N -B -e "SELECT VERSION();" 2>/dev/null)

if -z "$VER_RAW" ; then

err "获取版本失败"

restore_cfg

exit 1

fi

info "版本:VER_RAW" case "VER_RAW" in

MariaDB ) FLAVOR="mariadb" ;;

*) FLAVOR="mysql" ;;

esac
NUM=(printf '%s\\n' "VER_RAW" | sed 's/^5.5.5-//'

| grep -oE '0-9+.0-9+.0-9+' | head -1)

MAJOR={NUM%%.*}; R={NUM#* .}; MINOR={R%%.*}; PATCH={R#* .}

MAJOR={MAJOR:-0}; MINOR={MINOR:-0}; PATCH=${PATCH:-0}

info "识别:FLAVOR MAJOR.MINOR.PATCH"
ver_ge(){

"$MAJOR" -gt "$1" && return 0

"$MAJOR" -lt "$1" && return 1

"$MINOR" -gt "$2" && return 0

"$MINOR" -lt "$2" && return 1

"$PATCH" -ge "$3" && return 0

return 1

}

---------- 9. 读取账号与运行态 ----------

USERS=$(sql -N -B -e "SELECT CONCAT(user,'@',host) FROM mysql.user WHERE user='root';" 2>/dev/null)

CREATE_ROOT=0

if -z "$USERS" ; then

warn "未查到 root 账号,将创建 root@localhost"

CREATE_ROOT=1

else

info "待重置账号:(printf '%s\\n' "USERS" | tr '\n' ' ')"

fi
RO_FIX=""

ROV=$(sql -N -B -e "SELECT @@global.read_only;" 2>/dev/null)

"$ROV" = "1" && RO_FIX="$RO_FIX SET GLOBAL read_only=OFF;"

SROV=$(sql -N -B -e "SELECT @@global.super_read_only;" 2>/dev/null)

"$SROV" = "1" && RO_FIX="$RO_FIX SET GLOBAL super_read_only=OFF;"
PW_RELAX=""

if "$FLAVOR" = "mysql" ; then

if ver_ge 8 0 0; then

COMP=$(sql -N -B -e "SELECT COUNT(*) FROM mysql.component WHERE component_urn LIKE '%validate_password%';" 2>/dev/null)

if "${COMP:-0}" -gt 0 2>/dev/null; then

PW_RELAX="SET GLOBAL validate_password.policy=LOW; SET GLOBAL validate_password.length=4;"

fi

elif ver_ge 5 7 0; then

VP=$(sql -N -B -e "SHOW VARIABLES LIKE 'validate_password_policy';" 2>/dev/null)

-n "$VP" && PW_RELAX="SET GLOBAL validate_password_policy=LOW; SET GLOBAL validate_password_length=4;"

fi

fi

---------- 10. 按版本生成改密语句 ----------

make_stmt(){

local u="{1%@*}" h="{1#* @}"

if "$FLAVOR" = "mariadb" ; then

if ver_ge 10 2 0; then

echo "ALTER USER 'u'@'h' IDENTIFIED BY '$NEW_PASS';"

else

echo "SET PASSWORD FOR 'u'@'h' = PASSWORD('$NEW_PASS');"

fi

else

if ver_ge 5 7 6; then

if -n "$AUTH_PLUGIN" ; then

echo "ALTER USER 'u'@'h' IDENTIFIED WITH AUTH_PLUGIN BY 'NEW_PASS';"

else

echo "ALTER USER 'u'@'h' IDENTIFIED BY '$NEW_PASS';"

fi

else

echo "SET PASSWORD FOR 'u'@'h' = PASSWORD('$NEW_PASS');"

fi

fi

}

---------- 11. 单连接执行(关键) ----------

OUT=$(

{

echo "FLUSH PRIVILEGES;"

-n "$RO_FIX" && echo "$RO_FIX"

-n "$PW_RELAX" && echo "$PW_RELAX"

if "$CREATE_ROOT" -eq 1 ; then

if "$FLAVOR" = "mysql" && ver_ge 8 0 0; then

echo "CREATE USER 'root'@'localhost' IDENTIFIED BY '$NEW_PASS';"

echo "GRANT ALL PRIVILEGES ON . TO 'root'@'localhost' WITH GRANT OPTION;"

else

echo "GRANT ALL PRIVILEGES ON . TO 'root'@'localhost' IDENTIFIED BY '$NEW_PASS' WITH GRANT OPTION;"

fi

else

for uh in USERS; do make_stmt "uh"; done

fi

echo "FLUSH PRIVILEGES;"

} | sql --force 2>&1

)

-n "$OUT" && printf '%s\n' "$OUT"
FAILED=0

case "$OUT" in "ERROR") FAILED=1 ;; esac

---------- 12. 老版本兜底 ----------

if "$FAILED" -eq 1 && "$CREATE_ROOT" -eq 0 ; then

if "$FLAVOR" = "mysql" && ver_ge 5 7 6; then

warn "标准语句失败,请按上面的 ERROR 处理(多为密码策略校验,可改用更强密码)"

else

warn "标准语句失败,改为直接更新权限表..."

{

for uh in $USERS; do

u="{uh%@*}"; h="{uh#* @}"

if "$FLAVOR" = "mariadb" && ver_ge 10 4 0; then

echo "SET PASSWORD FOR 'u'@'h' = PASSWORD('$NEW_PASS');"

else

echo "UPDATE mysql.user SET Password=PASSWORD('NEW_PASS') WHERE user='u' AND host='$h';"

fi

done

echo "FLUSH PRIVILEGES;"

} | sql --force 2>&1

fi

fi

---------- 13. 还原并验证 ----------

info "还原配置并重启..."

restore_cfg

trap - INT TERM
info "等待实例就绪..."

i=0

while "$i" -lt 60 ; do

MYSQL_PWD="NEW_PASS" "MYSQL_BIN" $CONN_OPTS -uroot

--connect-timeout=5 -e "SELECT 1" >/dev/null 2>&1 && break

i=$((i+1)); sleep 2

done
if MYSQL_PWD="NEW_PASS" "MYSQL_BIN" $CONN_OPTS -uroot

--connect-timeout=5 -e "SELECT 1" >/dev/null 2>&1; then

info "完成:新密码验证通过"

exit 0

else

warn "新密码验证失败,请检查错误日志与上面的输出"

warn "配置备份:$BK"

exit 1

fi

使用示例

bash reset_all.sh --help

bash reset_all.sh -V

bash reset_all.sh -c /etc/my.cnf 'MyN3wP@ss'

MYSQL_NEW_PASS='MyN3wP@ss' bash reset_all.sh