在MySQL的权限体系里,权限并不是集中存放在一张表中,而是按照作用范围拆成了多张系统表。其中user表负责全局级别的权限控制,db表负责单个数据库的级别权限控制。当同一个用户既在user表中有记录,又在db表中有记录时,很多人会困惑:如果两边对同一个权限的设置不一样,MySQL究竟听谁的。要回答这个问题,必须先弄清楚MySQL在建立连接和执行语句时,是如何逐级检查这些表的。

一、user表与db表的职责划分
user表位于mysql数据库中,它的每一行代表一个账户在全部数据库、全部表上的全局权限。比如你在user表里给appuser开了SELECT权限,且Host为%,那就意味着这个用户从任意主机连上来,都能读所有库的所有表。全局权限字段如Select_priv、Insert_priv等都是枚举类型的Y或N,结构简单但影响面极广。
相比之下,db表用来做更细粒度的控制。它在user表的基础上增加了Db字段,表示这条权限只对某一个数据库生效。例如db表中有一行appuser对report_db库拥有SELECT和INSERT权限,但user表里该用户只有SELECT全局权限。此时用户在report_db上能插入数据,在其他库只能查询,这就是层级叠加的效果。
两张表并不是谁覆盖谁的关系,而是范围由宽到窄的互补结构。MySQL官方文档将其称为权限系统的层级模型:全局层、数据库层、表层、列层。当请求到达时,服务器从最上层开始向下检查,只要某一层明确拒绝了操作,请求就失败;如果上层允许但下层有更宽授权,则以下层补充为准。这种设计让管理员既能用user表做统一管控,又能用db表做差异化分配。
二、权限校验时的优先级执行逻辑
MySQL在用户执行每条SQL前,都会调用内部的权限检查函数。以查询为例,首先检查user表对应账户的Select_priv。如果是Y,直接放行,不再看db表;如果是N,再去db表找该用户针对当前库的Select_priv,若为Y则放行,否则拒绝。这说明user表的全局Y具有最高通行力,而全局N并不阻断db表单独授予的Y。
我们可以用一段模拟逻辑来理解这个过程。下面的伪代码展示了MySQL内核大致的判断顺序:
-- 模拟MySQL权限判断逻辑
SELECT Select_priv INTO @global_priv FROM mysql.user
WHERE User = 'appuser' AND Host = '192.168.0.1';
IF @global_priv = 'Y' THEN
-- user表全局允许,直接通过
SIGNAL SQLSTATE '00000' SET MESSAGE_TEXT = 'allowed by user table';
ELSE
-- 退而查db表
SELECT Select_priv INTO @db_priv FROM mysql.db
WHERE User = 'appuser' AND Host = '192.168.0.1' AND Db = 'report_db';
IF @db_priv = 'Y' THEN
SIGNAL SQLSTATE '00000' SET MESSAGE_TEXT = 'allowed by db table';
ELSE
SIGNAL SQLSTATE '28000' SET MESSAGE_TEXT = 'access denied';
END IF;
END IF;
从上面逻辑能看出,所谓冲突其实很少发生,因为大部分权限字段是独立的Y或N。真正的冲突场景出现在:管理员以为改了user表就能限制某库,但db表里还留着旧授权,结果用户依然能写数据。反过来,如果在user表给了全局ALL,又想在db表收回某个库,那是做不到的,因为全局Y已经提前放行。
另外要注意,MySQL在匹配user和db表时,Host字段的匹配也参与优先级。最精确的主机匹配胜出,比如192.168.0.1的记录优先于%。所以排查权限问题时,不能只看用户名的权限,还要确认连接进来的Host命中了哪一行。
三、常见权限冲突场景与排查实践
实际运维中,最典型的冲突是回收权限不彻底。比如早期给开发同学在user表开了全局SELECT,后来想限制只能看test库,于是在db表只加了test库的记录,却忘了user表还是Y。此时用户照样能读生产库,造成看起来像权限没生效的错觉。正确做法是先用REVOKE在全局层收回,再在db层授予所需库。
另一个场景是php应用报Access denied for user,但命令行能登。这往往因为命令行从localhost连,命中了user表里localhost的宽松记录;而php从127.0.0.1连,命中了另一条更严格的db表记录。排查时应当执行下面的语句,把相关表都列出来比对:
-- 查看用户在user表中的全局权限 SELECT Host, User, Select_priv, Insert_priv, Update_priv FROM mysql.user WHERE User = 'appuser'; -- 查看用户在db表中的库级权限 SELECT Host, Db, User, Select_priv, Insert_priv, Update_priv FROM mysql.db WHERE User = 'appuser'; -- 查看实际生效的合并权限 SHOW GRANTS FOR 'appuser'@'127.0.0.1';
SHOW GRANTS命令输出的就是MySQL把user表和db表合并后,实际对该账户生效的授权语句。如果发现全局有ALL PRIVILEGES,但db表却限制了某个库,要明白这种限制是无效的。想要真正隔离,必须先从全局层REVOKE,再按库授权。
总结来说,MySQL处理user表与db表权限时,采用的是全局优先通过、库级补充授权的层级模型,而不是后写覆盖先写。理清这一点,才能在权限调整时避免遗漏,也能在出现访问异常时快速定位是哪一层配置导致冲突。