☰
MySQL用户管理实战:创建用户、授权模型与生产规范
2026/10/7 3:34:48 网站建设 项目流程

接手过不少团队的历史库,也处理过几次让人头疼的权限事故。说句实话,MySQL 用户管理本身不难,难的是大多数人对它的理解停留在“CREATE USER + GRANT 就完事”的层面,等报告问题的时候才发现:账号对应不上业务、密码过期了没人知道、权限开得比谁都大。这篇文章就围绕 MySQL 用户管理这摊事,把创建用户、授权模型、生产规范、常见故障排查整条链路拆开讲清楚。不管你是刚入门的新手,还是日常要管几套库的运维,都能从这里拿到可以直接照做的步骤和思路。

1. 用户管理到底在管什么:先看清全貌再动手

1.1 用户管理不只是“建账号给权限”

很多人在 MySQL 上花时间最多的是优化 SQL、调参、搞主从,用户管理往往被丢到角落里。但用户管理实际上是数据库安全的第一道门,它至少包含下面这五件事:

  • 账号生命周期管理:创建、修改密码、加锁解锁、删除账号。
  • 访问来源控制:限制某个账号只能从哪些 IP 或主机连接,这就是 host 字段的用途。
  • 权限模型设计:决定账号能读哪些库、写哪些表、能不能建索引、能不能执行存储过程。
  • 密码与认证策略:密码复杂度、过期时间、认证插件是老的 mysql_native_password 还是新的 caching_sha2_password。
  • 审计与回收:定期检查谁在用、谁变成了僵尸账号、权限是不是给超了。

这些内容全部围绕一张表展开,就是系统库里的 mysql.user 表,以及配套的 mysql.db、mysql.tables_priv、mysql.columns_priv。理解了这几张表,用户管理的全貌基本就清晰了。很多人遇到权限问题时喜欢拍脑袋猜,其实所有答案都在这些权限表里。

1.2 用户管理难在哪里

用户管理之所以容易出乱子,我观察下来主要是三个原因。

第一,权限模型是分层的,不是“给了就完事”。MySQL 权限从全局层一直延伸到库、表、列、存储过程,各层权限之间是叠加关系。比如对某个库授予了 all privileges,又单独 revoke 了某张表的 delete 权限,实际效果可能是表级 revoke 生效,也可能是被库级权限覆盖,取决于权限检查的顺序和精确匹配规则。如果不理解这个模型,排查时就会绕晕。

第二,多人协作时用户管理最容易失去控制。一个业务系统上线,开发、测试、运维各自建账号,没有统一登记;人员变动之后账号也不回收,时间一长,库里几十个账号,没人说得清哪个是哪个。我见过一个统计系统居然用 root 账号跑任务,一问原因,没人知道密码是什么,只是配置文件里一直就是这么写的。

第三,MySQL 版本演进带来的差异。MySQL 5.7 能用 GRANT 语句顺带创建用户,到了 8.0 就必须先 CREATE USER 再 GRANT;默认认证插件也从 native_password 切换成 caching_sha2_password。如果团队里有人守着旧习惯不放,很容易出现“明明是权限问题,实际上是认证插件不兼容”的假故障。

所以这篇文章与其说是教几条命令,不如说是帮大家建立一套处理用户管理的完整思路:从建号开始,到授权、巡检、排错,每一步都知道自己在做什么、为什么这么做。

2. 创建用户:CREATE USER 里那些容易被忽略的细节

2.1 MySQL 8.0 和 5.7 创建用户的方式完全不同

先提醒一个最容易踩的版本坑。在 MySQL 5.7 及更早版本,你可以直接写:

GRANT SELECT ON mydb.* TO 'app'@'%' IDENTIFIED BY 'password';

这行语句会顺带把用户 app 建出来。但在 MySQL 8.0 里,这条语句会直接报错,因为 GRANT 语句不再具备创建用户的功能,必须先创建再授权:

CREATE USER 'app'@'%' IDENTIFIED BY 'password'; GRANT SELECT ON mydb.* TO 'app'@'%';

很多从 5.7 升到 8.0 的团队,头一个遇到的兼容问题就是这个。在写自动化脚本时,一定要带上版本判断,否则升级后脚本大面积失效。

顺手看一眼 MySQL 8.4 LTS 之后对 mysql_native_password 的处理也比较关键。这个老认证插件默认被禁用,如果你要创建使用旧插件的账号,得先显式启用再明确指定插件:

CREATE USER 'legacy_app'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

不过实务上我的建议是:除非有老客户端驱动实在没法升级,否则新账号一律用默认的 caching_sha2_password,别给自己留技术债。

2.2 host 字段:'localhost' 和 '%' 的真实区别

创建用户的时候第二重要的就是 host。很多人直接写'app'@'%',觉得这样省事,但%的含义是“可以从任意主机连接”,它包含了一个非常隐蔽的问题:当服务端有多个匹配规则时,MySQL 会按主机匹配的精确度来选。

举一个真实案例。有个用户同时存在'app'@'localhost'和'app'@'%'两条记录,应用从本机连接时,MySQL 会优先匹配'app'@'localhost',而不是你用来授权的'app'@'%'。如果'app'@'localhost'记录里的密码或权限和预期不符,就会出现“明明改了权限却还是失败”的现象。

所以建用户之前先想清楚这个账号到底从哪里连:

  • 应用和数据库在同一台机器上,用'app'@'localhost'。
  • 应用服务器有固定内网 IP,写具体的 IP 或网段,比如'app'@'192.168.10.%'。
  • 实在没法固定来源,才退而求其次用'%',同时配合防火墙兜底。

查看当前所有用户和来源,最直接的方式是:

SELECT User, Host, plugin, account_locked, password_expired FROM mysql.user;

这条 SQL 应该成为你接手任何一套 MySQL 时执行的第一条命令。

2.3 认证插件不只是“兼容性”问题

MySQL 8.0 默认的 caching_sha2_password 比旧的 native_password 更安全,但很多老客户端不支持,典型表现是:应用连接时报Authentication plugin 'caching_sha2_password' cannot be loaded,或者使用很老的 PHP、Connector/J 版本建立连接失败。

遇到这种情况,建议优先升级客户端驱动。如果确实升级不了,可以用如下语句把账号切回老插件:

ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

这里我多提醒一句:你说的“驱动不支持新插件”只是表象,本质往往是驱动太老。与其改变服务器端的安全策略迁就它,不如推动升级。数据库侧尽量保持安全的默认策略,不让老技术拖后腿。

3. 权限分配:GRANT 背后的模型和判断逻辑

3.1 权限的四个层级,先搞清楚再收放

MySQL 权限大致分成四层,理解这四层你才能准确判断“一条 GRANT 到底影响什么”:

  • 全局权限:作用于整个实例,对应 mysql.user 表,比如 CREATE USER、PROCESS、SUPER、RELOAD,以及*.*上的 SELECT、INSERT 等。这类权限影响最大,要格外谨慎。
  • 库级权限:作用于某个库,对应 mysql.db 表,写在dbname.*上。
  • 表级权限:作用于具体表,对应 mysql.tables_priv 表,比如dbname.tablename上的 SELECT、INSERT、ALTER。
  • 列级权限:作用于表中特定的列,对应 mysql.columns_priv,比如只允许某个账号读取用户的手机号字段而不能读身份证字段。

权限检查时,MySQL 会按“全局 → 库 → 表 → 列”的顺序做叠加,最终生效的权限是各层权限的并集。举个例子,库级给了 SELECT,表级 revoke SELECT,最后表级的 SELECT 依然存在,因为 MySQL 权限不是“后给覆盖先给”,而是“不同层级取并集”。这点不搞清楚,排查权限问题时会走弯路。

查询权限最可靠的方式是 SHOW GRANTS:

SHOW GRANTS FOR 'app'@'%';

它会把该账号所有层级的授权完整列出来,是排障的第一工具。想看得更细,直接查三个表也行:mysql.user、mysql.db、mysql.tables_priv。

3.2 GRANT 语法细节和 WITH GRANT OPTION 的风险

授权的基本语法是:

GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app'@'192.168.10.%';

这里有几条容易忽视的关键点。

第一,不建议轻易给 ALL PRIVILEGES。很多人图省事,把 all on*.*一次性授出去,等于给了整个实例的全部权限,这基本就是在制造事故。即使要给某个库的全权,也建议看看到底是什么目的。

第二,WITH GRANT OPTION 是很多人容易忽略的隐藏风险。它表示该账号能把“自己拥有的权限”再授予别人。比如你给一个开发账号授了 SELECT WITH GRANT OPTION,他就能把 mydb 的 SELECT 转授给其他账号。这不是简单的“给权限”,而是给了“分配权限的能力”,在审计上会带来连锁麻烦。个人经验:除非有明确的管理需求,否则一律不附加 WITH GRANT OPTION。

第三,REVOKE 和 GRANT 一样需要写“回收范围”。要从库级回收某个权限,就写:

REVOKE DELETE ON mydb.* FROM 'app'@'192.168.10.%';

别只写REVOKE DELETE ON mydb.*然后把用户列表漏掉,那语句根本不会执行成功。回收权限不会自动删除账号,要删账号必须 DROP USER。

3.3 最小权限原则的落地例子

说了这么多原则,给一个可以直接参考的实践案例。假设有一个订单系统,库名是 biz_order,业务方需要三种账号:

业务只读账号,供报表和数据统计使用:

CREATE USER 'order_report'@'192.168.20.%' IDENTIFIED BY 'xxxx'; GRANT SELECT ON biz_order.* TO 'order_report'@'192.168.20.%';

业务读写账号,供应用服务使用:

CREATE USER 'order_app'@'192.168.10.%' IDENTIFIED BY 'yyyy'; GRANT SELECT, INSERT, UPDATE, DELETE ON biz_order.* TO 'order_app'@'192.168.10.%';

管理运营账号,需要额外的事务与临时表等操作权限,但不需要操作其他系统库:

CREATE USER 'order_dba'@'192.168.30.%' IDENTIFIED BY 'zzzz'; GRANT ALL PRIVILEGES ON biz_order.* TO 'order_dba'@'192.168.30.%';

三条规则对应三组语句,互相不交叉。运维侧只需在接入层或防火墙对该网段做区分,就能做到“一个账号只干一件事”。这套模型的优点是:出了任何数据安全事件,可以通过账号精确锁定来源,而不是喊一声“谁动了我的表”然后全员排查。

4. 生产环境用户管理的常态化动作:巡检、收敛、变更

4.1 给账号做一次“人口普查”

如果库里已经积累了大量账号,建议先做一次彻底的资产梳理。用下面几条 SQL 把基础情况摸清楚:

-- 所有账号和来源 SELECT User, Host, plugin, account_locked, password_expired FROM mysql.user WHERE User NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema', 'root'); -- 每个账号有哪些全局权限 SELECT User, Host, Grant_priv, Super_priv, Process_priv, Reload_priv FROM mysql.user; -- 每个账号对应的库级权限 SELECT Db, User, Host, Select_priv, Insert_priv, Update_priv, Delete_priv FROM mysql.db;

梳理完以后,给每个账号打标签:属于哪个业务、哪个负责人、创建时间、最近连接时间。最近连接时间可以从performance_schema.accounts或者sys.session辅助判断。如果一个账号超过 90 天没人连过,主动联系业务方确认还能不能回收。这是收敛账号的第一步。

4.2 账号命名与权限存档

没有规范命名的账号,后期一定认不出来。我见过abc、test123、user1这种账号,问谁都不知道是谁建的,最后只能清理掉。规范的命名至少应包含“用途前缀-业务名-环境”,比如:

  • report_order_prod:订单业务生产库只读账号
  • app_order_prod:订单业务生产库读写账号
  • dev_zhangsan:某个开发人员的个人开发账号

权限变更记录也非常重要。每次 GRANT 或 REVOKE 的执行语句、操作人、变更原因、生效时间,都应记录在案。没有记录,几天后你只能靠SHOW GRANTS倒推,没法知道当初为什么这么授。

4.3 密码生命周期和明文密码管理

密码管理上见过最多的坑是两个:密码永不过期、密码写死在应用配置里。对于前者,MySQL 支持设置全局过期策略:

SET GLOBAL default_password_lifetime = 90;

这会让之后创建的用户默认 90 天必须改一次密码。对于已存在的用户,可以单独设置:

ALTER USER 'order_app'@'192.168.10.%' PASSWORD EXPIRE INTERVAL 90 DAY;

需要注意,密码过期后应用连接会报Your password has expired,所以上线这种策略前务必和应用开发确认客户端是否能处理密码更新。建议在测试环境先跑一个周期,摸清所有连接的报错特征后再推到生产。

明文密码的管理建议统一放到密钥管理服务或者配置中心里,不要放在代码仓库的明文配置里。数据库账号一旦泄露,影响的不是单个服务,而是整个数据链路。

5. 用户管理常见问题排查链路:从报错倒推到根因

5.1 权限改了为什么不生效

改完权限之后不生效,是提问频率最高的一类问题。先说结论:从 MySQL 5.7 开始,CREATE USER、GRANT、REVOKE、DROP USER 这类账号操作会直接更新权限表并生效,不需要FLUSH PRIVILEGES。只有一种情况需要手动执行 FLUSH PRIVILEGES,那就是直接去 INSERT、UPDATE、DELETE 了 mysql.user 等权限表。

所以排查“权限改了没生效”时,按这个顺序查:

  1. 确认你改的是不是当前连接匹配的那条用户记录。用 SHOW GRANTS FOR CURRENT_USER() 看当前会话实际拥有的权限。
  2. 确认是否存在多条 host 记录,比如'app'@'localhost'和'app'@'%',MySQL 实际匹配到的可能是另一条。
  3. 确认连接池是否把旧连接缓存住了。连接池里已有的连接在事务周期内不会重新获取权限,需要重启应用或等连接池连接老化。
  4. 检查是否有触发器等对象级权限例外,比如某个存储过程默认以 DEFINER 身份执行,和你给账号授的权限没有直接关系。

还有一个隐蔽地方:如果你修改的是全局权限,比如 PROCESS、SUPER,这些权限对已经存在的连接生效要等连接重新建立。所以遇到权限调整后行为不变的,最直接的办法是让应用重连一次,而不是死磕服务器端。

5.2 Access denied 的几种常见原因

ACCESS DENIED是用户管理里最常见的报错,但同样一句报错背后原因可能完全不同。我习惯把原因拆成四类:

第一,密码错误。这个最直白,重置密码即可。但要注意 MySQL 对密码大小写敏感,而且密码里包含特殊字符时,命令行传参经常会出问题。

第二,host 不匹配。报错原文会带using password: YES之外的关键信息。假设你从 192.168.10.55 连接,但库里只有'app'@'localhost'和'app'@'192.168.20.%',那就会拒绝连接。此时用SELECT User, Host FROM mysql.user WHERE User='app';确认是否缺少对应网段的 host,再补一条即可。

第三,账号被锁或者密码过期。MySQL 支持连续多次登录失败导致账号临时锁定,也支持密码过期策略。报错里如果出现Account is locked或Your password has expired,定位就很清楚。

第四,认证插件不兼容。老客户端连新 MySQL 8.0 时,报错是插件无法加载。新客户端偶尔也有兼容问题,比如 Connector/J 版本 < 8.0 默认使用 native_password,连 MySQL 8.0 默认插件就跑不起来。

排查时建议直接打开通用日志观察认证阶段:

SET GLOBAL general_log = 'ON'; SET GLOBAL log_output = 'TABLE'; SELECT * FROM mysql.general_log ORDER BY event_time DESC LIMIT 10;

看到具体报错后关闭日志,避免占用磁盘空间。

5.3 忘记密码后的恢复步骤

最后一个高频问题:root 密码忘了。这里给一套我自己验证过多次的恢复流程,核心思路是让 MySQL 跳过权限认证启动,再重建密码。

第一步,停止 MySQL 服务:

systemctl stop mysqld

第二步,以跳过授权表的方式启动。先在配置文件中临时加入一行:

[mysqld] skip-grant-tables

然后启动服务:

systemctl start mysqld

第三步,登录并重建密码:

mysql -u root ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

如果跳过授权表模式下 ALTER USER 报错,通常是因为权限表尚未初始化完成,可以加上 FLUSH PRIVILEGES 之后再执行。

第四步,删掉配置文件里的skip-grant-tables,重启服务。

这里必须强调风险:skip-grant-tables意味着 MySQL 完全不校验任何账号密码,任何人只要知道端口就能连上来。这个模式只能在内网且服务短暂离线时使用,操作期间不要对外暴露端口。恢复完成之后第一时间移除配置并重启。如果有审计要求,最好直接查看 mysql.general_log 确认恢复期间有没有异常连接。

写在最后的实操体会

用户管理这件事,只要坚持三个习惯,就能避免绝大多数事故:建用户时想清楚 host 和用途,授权时坚持最小权限,定期做账号巡检和权限归档。我自己的习惯是每次给新项目建完账号,第一时间把 SQL 和负责人信息登记到团队文档里,三个月后回头查,价值非常明显。最后再分享一个小技巧:巡检时多用 SHOW GRANTS,不要只看授权表,因为存储过程、函数、事件这些对象的执行权限和表权限是分开记录的,只看某几张表很容易漏掉细粒度授权,也容易在排查时被误导。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询