☰
查mysql.user表:一次搞懂用户密码哈希与权限排查
2026/10/21 7:30:57 网站建设 项目流程

搞MySQL的人早晚会在mysql.user表里栽几个跟头。我印象最深的一次,某业务系统报“密码错误”,开发同事反复确认密码没输错,我上去查了一圈才发现,根本不是密码问题,而是账户匹配到了错误的 host 组合,连密码哈希都还没来得及比对就被拒了。后来我写了个习惯:凡是涉及登录、权限、密码的故障,第一件事永远是查mysql.user里的user、host、plugin、authentication_string这四列。今天就把这套东西从头到尾讲透,包括 MySQL 内置系统表怎么分工、用户密码哈希存在哪、hash 格式长什么样、怎么查、以及线上踩过的坑。

这篇文章适合三类人:日常被数据库连接问题折磨的后端开发、要管服务器又要管库的运维、还有刚接触 MySQL 权限机制的初学者。读完你不仅能看懂authentication_string里的那串乱码,还能照着 SQL 自己排查账户问题。

1. MySQL的系统库全家桶:先分清职责再动手

很多人刚接触时都会困惑:连上 MySQL,SHOW DATABASES;会看到mysql、information_schema、performance_schema、sys这些库,它们到底谁管什么?其实这四个库就是 MySQL 的“内部管理系统”,各管一摊,分清楚了,后面查用户 hash 的时候才知道去哪个库找数据。

1.1 mysql库:账户权限的核心仓库

mysql库是整个 MySQL 实例的“管理中枢”,里面存放的全是元数据。我们最关注的用户账户、权限信息、密码哈希都存在这个库里。核心表包括:

  • user:用户账户、host、密码哈希、插件、账户状态、资源限制。这是本文的绝对主角。
  • db:用户在某数据库上的权限。
  • tables_priv:用户在某个表上的权限。
  • columns_priv:用户在某个列上的权限。
  • procs_priv:存储过程和函数上的权限。
  • global_grants:一些动态全局权限,MySQL 8.0 新增。
  • servers、tablespaces、event、time_zone等,日常用得少,但也是系统表的一部分。

需要特别强调的是:mysql库里的表绝大多数是MyISAM存储引擎(MySQL 8.0 以后部分表改成了 InnoDB),它的数据文件就在数据目录下的mysql/子目录里。所以如果备份时只备份业务库,漏掉mysql库,等于把所有用户密码哈希、权限配置都丢掉了,恢复业务库后却发现连不进去,这种事故我见过不止一次。

1.2 information_schema:MySQL的“词典视图”

information_schema严格说不是存储数据的库,而是一组“视图”,实时从 MySQL 各个模块读取信息。它没有对应的数据目录文件,你也没法直接往里面写数据。

它是兼容数据库行业标准的东西,核心价值在于查询元数据。常用视图有:

  • TABLES:所有库的表、行数估算、表类型、字符集。做容量评估、巡检脚本时高频使用。
  • COLUMNS:所有表的字段信息,包括字段类型、是否允许 NULL、默认值、注释。
  • STATISTICS:索引信息,联合索引字段顺序、唯一性等。
  • USER_PRIVILEGES:全局权限的视图,能直接看到当前有哪些全局授权。
  • SCHEMA_PRIVILEGES、TABLE_PRIVILEGES、COLUMNS_PRIVILEGES:库、表、列权限视图,配合权限排查非常顺手。
  • PROCESSLIST:当前连接线程,相当于SHOW PROCESSLIST的信息来源。
  • INNODB_TABLES、INNODB_INDEXES等:InnoDB 引擎内部结构信息。

举个例子,我想看某个用户在所有库里都有哪些权限,一条 SQL 就查出来了:

SELECT GRANTEE, TABLE_SCHEMA, PRIVILEGE_TYPE FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE LIKE "'app_user'%";

这个库最大的价值是“用 SQL 获取原本要猜的信息”,尤其写自动化巡检脚本的时候,information_schema几乎是必备数据源。

1.3 performance_schema 与 sys:监控与辅助

performance_schema是 MySQL 5.6 开始引入的“性能监控数据库”,它在 MySQL 内部收集 wait、lock、I/O、语句执行等事件。它不是给人直接看报表的,而是给 DBA 排查性能瓶颈用的。我查慢查询分布、锁等待来源时,经常从events_statements_summary_by_digest里捞数据。但这个库的表巨多,新手通常只记得performance_schema这个名字就好,具体业务不涉及性能分析时,不要乱动它的开关参数。

sys库是后期加进来的“便捷工具库”,本质是建立在performance_schema之上的一组视图、存储过程和函数。它的表名和视图名非常直白,比如:

  • sys.user_summary:按用户汇总当前连接数、总执行时间,查询用户级负载很好用。
  • sys.session:当前会话的情况,比直接看PROCESSLIST多了一层统计。
  • sys.schema_table_lock_waits:表锁等待相关。
  • sys.innodb_lock_waits:行锁等待排队信息。

所以这四个库的关系可以这样理解:mysql库是“账本”,记录谁是谁、能干什么;information_schema是“字典”,描述数据库里有什么;performance_schema是“监控仪表盘”,记录正在发生什么;sys是“快捷菜单”,把监控数据整理成好读的报表。查用户 hash 只需要盯着mysql.user表,但排查权限问题时常需要配合information_schema各个权限视图一起看。

2. mysql.user核心字段:密码哈希到底藏在哪

mysql.user表是整个权限体系的心脏。一行记录代表一个“账户”,账户不是只看用户名,而是user + host 的组合。这一点特别容易出问题,很多“密码正确却连不上”的故障就是 host 组合没匹配上。

2.1 从 password 字段到 authentication_string

历经多个大版本,MySQL 存储密码哈希的字段发生了明显变化:

MySQL 版本密码字段说明
5.6Password经典mysql_native_password,40位*开头十六进制哈希
5.7Password与authentication_string并存Password字段保留但推荐用authentication_string
8.0authentication_string彻底移除Password字段,所有插件哈希统一存这里

所以如果你在不同版本的库上跑同一条 SQL,很容易翻车。比如 8.0 上执行SELECT Password FROM mysql.user;直接报错 Unknown column,而 5.6 上SELECT authentication_string FROM mysql.user;可能返回 NULL 因为老库只写了Password字段。这是我第一条经验:查用户密码哈希,先确认版本,再决定字段,最省事的写法是直接看authentication_string,但老库要做兼容处理。

2.2 plugin:决定哈希生成方式的开关

很多新手只看authentication_string的长度和前缀,忽略plugin字段,结果连不上也不知道为什么。mysql.user.plugin表示这个账户用什么认证插件,它不仅决定密码校验方式,还直接决定了authentication_string里的字符串格式。常见值有三个:

  • mysql_native_password:老牌插件,哈希是*加 40 位大写十六进制。兼容性极好,老驱动、老客户端都认它。
  • caching_sha2_password:MySQL 8.0 默认插件,哈希以$A$开头。安全性更高,但老版本客户端(比如 5.x 的 libmysqlclient)连不上,报错信息里往往有Authentication plugin 'caching_sha2_password' cannot be loaded的字样。
  • sha256_password:另一个基于 SHA-256 的插件,通常用于 SSL 加密连接场景,哈希也是$开头但标志不同。

说个我踩过的坑:有一次我在 8.0 上新建用户,没写任何插件参数,默认就是caching_sha2_password,结果某台老跳板机上的脚本连不上。当时第一反应是密码写错了,折腾半天,最后在mysql.user里一看 plugin 才反应过来。后面的解决方案是把那个用户改成mysql_native_password或升级客户端驱动,视业务可接受度选择。

2.3 容易被忽略的状态字段

除了 user、host、plugin、authentication_string,mysql.user里还有几个日常排查时很容易被忽略但是很重要的字段:

  • account_locked:账户锁定标记。字段是'N'表示正常,'Y'表示锁定。锁定账户即使密码完全正确也会报 Access denied。
  • password_expired:密码是否已过期。'N'为正常,'Y'表示客户端需要重置密码才能继续操作。遇到“登录成功了却什么 SQL 都执行不了”的情况,多半是这个字段。
  • password_lifetime:密码有效期,单位是天,默认 NULL 表示用全局配置。
  • ssl_type、ssl_cipher:是否强制 SSL 连接,部分安全要求高的环境会用到。
  • max_questions、max_updates、max_connections、max_user_connections:资源限制,某些用户会因为连接数超限被拒,但报错通常不是密码错误而是 Too many connections。

我建议写巡检脚本的时候,把这几个字段一起带上:

SELECT user, host, plugin, authentication_string, account_locked, password_expired, password_lifetime FROM mysql.user;

一条 SQL 把账户状态看全。很多“灵异”问题,其实都是这些字段没查。

3. 用户hash查询实操:一条SQL顺藤摸瓜

了解了字段,实操就很简单。但“查 hash”不只是执行一条SELECT,后面还要会读、会验证、会延伸排查。

3.1 最常用的查询SQL组合

先看基本查询:

SELECT user, host, plugin, authentication_string FROM mysql.user WHERE user = 'app_user';

如果查不到结果,不要急着认为用户不存在,先去掉 where 条件看全表:

SELECT user, host, plugin, authentication_string FROM mysql.user;

因为app_user可能匹配的是'app_user'@'%',也可能匹配'app_user'@'localhost',它们完全是两行记录。只看 user 不看 host,是最常见的误判。

查空密码账户,这也是安全审计的必修课:

SELECT user, host, plugin, authentication_string FROM mysql.user WHERE authentication_string = '' OR authentication_string IS NULL;

查锁定的账户:

SELECT user, host, account_locked FROM mysql.user WHERE account_locked = 'Y';

查密码过期的账户:

SELECT user, host, password_expired, password_lifetime FROM mysql.user WHERE password_expired = 'Y';

3.2 hash格式解读:看到乱码别慌

拿到authentication_string里的值,不同插件读法完全不同。

mysql_native_password 格式

类似*6C387FC3893DBAF3D0D1403A7C1E2C9A8B7C6A1E,固定 41 个字符,以*开头。它其实是SHA1(SHA1(明文密码))的十六进制大写结果。可以自己验证一遍,用 Python 就能算:

import hashlib password = "your_password".encode() hash1 = hashlib.sha1(password).hexdigest().encode() hash2 = hashlib.sha1(hash1).hexdigest().upper() print("*" + hash2)

理论上能对上,就说明 hash 与明文匹配。不过实际排查中很少需要本地计算,因为 MySQL 自己会验证,但理解这个双重 SHA1 的过程,能帮助你明白一件事:MySQL 里存的不是密码明文,也不是可直接解密的密文,而是不可逆的摘要。所以任何人告诉你他可以从mysql.user里“解密”出原密码,基本都是在吹牛。

caching_sha2_password 格式

形如$A$005$3a1b2c...,MySQL 8.0 默认格式。$A$表示算法标识,005表示 5000 次迭代,后面是加盐后的哈希内容。这种格式比mysql_native_password安全得多,加了随机盐,因此同样的明文密码每次生成的 hash 都不一样。这也是不能拿mysql_native_password的*格式直接放进authentication_string里冒充caching_sha2_password的原因,插件不同,验证算法完全不同。

sha256_password 格式

类似$5$...或$SHA256$...,相对少见。日常运维一般碰不到,知道有这种格式即可。

3.3 顺着用户hash延伸的权限排查

密码哈希只是账户信息的一部分,排查权限问题时不能只盯着mysql.user。我常用的权限查询路线是这样的。

全局权限:

SELECT * FROM information_schema.USER_PRIVILEGES WHERE GRANTEE LIKE "'app_user'@'%'";

库级别权限:

SELECT GRANTEE, TABLE_SCHEMA, PRIVILEGE_TYPE FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE LIKE "'app_user'%";

表级别权限:

SELECT * FROM information_schema.TABLE_PRIVILEGES WHERE GRANTEE LIKE "'app_user'%";

列级别权限:

SELECT * FROM information_schema.COLUMN_PRIVILEGES WHERE GRANTEE LIKE "'app_user'%";

实操中,一条 SQL 就能把用户看得七七八八:

SELECT user, host, plugin, authentication_string, account_locked, password_expired FROM mysql.user WHERE user = 'app_user'; SELECT * FROM information_schema.USER_PRIVILEGES WHERE GRANTEE LIKE "'app_user'@'%'"; SELECT * FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE LIKE "'app_user'@'%'";

这三组查询组合起来,基本能回答“这个用户是谁、密码用哪种格式存、能不能连、连上来有哪些权限”这几个核心问题。

4. 实战排查与常见问题:避开这些坑

查 hash 是手段,解决实际问题才是目的。下面这些坑都是我实际踩过或帮别人排查过的,整理成速查思路。

4.1 登录失败排查的正确顺序

收到“用户连不上数据库”的反馈,我现在的排查顺序已经固定了,基本不会错:

  1. 先确认账户是否存在,user+host是否匹配当前连接来源。'app_user'@'localhost'和'app_user'@'%'不是一回事,MySQL 按 host 的精确度选匹配项,规则比较复杂,先按“最具体的优先”理解即可。
  2. 再看account_locked是否'Y',锁定的账户密码再对也进不来。
  3. 再看password_expired是否'Y',这种情况连接可能成功但后续操作受限,容易表现成“能连但什么都干不了”。
  4. 再看plugin是否和客户端驱动兼容,8.0 默认插件对老客户端不友好。
  5. 最后才怀疑密码哈希本身。实际上大部分“密码错误”都不是 hash 错了,而是前面几步的匹配问题。

举个例子,曾经有个程序用'app_user'@'10.0.0.%'的网段连接,而 MySQL 里只有'app_user'@'localhost'和'app_user'@'%',因为%被匹配到了,但权限和预期不一致。这种问题从密码角度永远找不到答案,必须回到 host 组合来看。

4.2 直接改authentication_string的雷区

网上很多“重置密码”的野路子让你直接UPDATE mysql.user SET authentication_string='xxx' WHERE user='yyy';。这个方法在特殊情况下有效,比如忘记 root 密码时可以配合skip-grant-tables来应急,但生产环境我强烈不建议把改 hash 当成常规手段。

直接改 hash 有几个隐藏问题:

  • 哈希必须与 plugin 匹配,格式错了照样登不上。
  • MySQL 8.0 里直接改authentication_string不会同步更新账户的认证插件相关信息,容易造成“hash 对了但登录逻辑还是不对”的诡异状态。
  • 绕过权限管理器的行为一旦出错,可能把整个权限表搞乱,恢复成本远高于正常重置。

正确做法永远是:

ALTER USER 'app_user'@'%' IDENTIFIED BY '新密码';

如果你确实需要把一个用户的哈希复制到另一个用户,不要拼字符串去 update,应该用 MySQL 官方的认证器语法,例如:

CREATE USER 'new_user'@'%' IDENTIFIED WITH mysql_native_password AS '*6C387FC3893DBAF3D0D1403A7C1E2C9A8B7C6A1E';

这样 hash 和 plugin 是一起设置的,不会出现“分离”问题。

4.3 匿名用户和host匹配的坑

匿名账户''@'localhost'是默认安装时潜在风险点,它匹配的是没有指定用户名的连接。如果你的业务程序连接字符串里用户名写错了,或者解析出了问题,可能就被匿名账户接住了,表现成“没有一个叫 app_user 的权限,但居然能连上且只有极少权限”。这类问题很隐蔽,排查时要查一下:

SELECT user, host, authentication_string FROM mysql.user WHERE user = '';

查出匿名账户后,确认不是故意配置,就清理掉。但注意清理前要看有没有业务依赖,部分老系统会在本机用无密码 socket 方式连库,直接删可能误伤。

4.4 其它高频问题速查表

症状可能原因排查/解决
ERROR 1045 (28000): Access denied for user密码错、host 不匹配、账户锁定按 4.1 顺序逐步查
连接报Authentication plugin 'caching_sha2_password' cannot be loaded客户端版本太老,不支持默认插件升级驱动,或改回mysql_native_password
能连上但执行任何 SQL 都提示需要重置密码password_expired='Y'ALTER USER ... IDENTIFIED BY ...;
密码正确但提示账号已锁定account_locked='Y'ALTER USER ... ACCOUNT UNLOCK;
程序查到用户但权限老是少host 匹配到了另一条记录检查user+host组合,不要只看 user
误改mysql.user导致无法登录权限表损坏用skip-grant-tables应急,通过官方ALTER USER修复,慎用直接 update

最后再说一个习惯上的建议。我把“用户审计”写进日常巡检脚本,定期跑一遍mysql.user,重点看:是否存在空密码账户、是否存在过期且未重置的密码、是否存在锁定后又被释放的账户、是否存在跨 host 的重复用户。这些看起来基础的检查,往往能在故障发生前就暴露问题。少一次半夜被叫醒,多睡几个好觉。

说实话,到现在我每次面对数据库连接类故障,第一反应仍然是去查mysql.user。不是因为它万能,而是因为它包含了最基础的账户状态和密码哈希信息。你把 user、host、plugin、authentication_string 这四个字段看明白了,MySQL 的登录认证机制就懂了一大半。剩下的权限细节,再顺着 information_schema 一层层往外查就行了。这套方法,从 5.6 排到 8.0,都好用。

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

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

立即咨询