MySQL的访问控制并不只是输入密码那一下,而是由连接建立和语句执行两个阶段共同完成。第一阶段负责确认你是谁以及你从哪里来,第二阶段负责确认你要做什么以及有没有授权。连接失败和权限不足问题经常被混在一起,根源就在于没有区分这两个阶段。例如用户能连接数据库,却在查询时提示命令拒绝,这通常不是密码错误,而是第二阶段没有对应的库表权限。

围绕这两个阶段展开,可以更清晰地理解MySQL如何通过权限表实现细粒度控制。本文会先拆解连接认证流程,再分析请求授权时权限表的匹配顺序,最后给出排障思路和账号安全实践。明白这些内容之后,面对ERROR 1045和ERROR 1142就能快速判断问题出在哪个环节。
一、连接阶段:用户认证与来源主机匹配
连接阶段发生在TCP连接建立之后。客户端发送认证包,其中包含用户名、来源主机、认证插件以及密码的加密结果。MySQL收到认证信息后,会从mysql.user表中查找匹配的账号记录。这里要特别注意,来源主机不是客户端自己填写的字符串,而是MySQL根据TCP连接的来源IP判断出来的。因此即使是同一个用户名,从不同机器连接也可能命中不同的账号记录。
当存在多条可能匹配的记录时,MySQL会按照主机和用户名的排序规则选择最精确的一条。比如Host为具体IP地址的记录优先于Host为网段的记录,网段记录又优先于%这种通配记录。这种设计保证了更小范围的授权能够覆盖更大范围的默认配置。开发者可以直接查看mysql.user表,观察Host列的取值,常见值包括localhost、127.0.0.1、192.168.10.%以及%。
认证插件也会影响连接阶段的判断。MySQL 8默认使用caching_sha2_password,而较早版本通常使用mysql_native_password。如果客户端驱动版本较旧,可能不支持默认插件,导致出现ERROR 1045。此时需要检查插件类型,并通过ALTER USER调整账号使用的认证插件。另外,如果开启了主机名反向解析,MySQL可能会尝试把来源IP解析为主机名,这也会导致连接阶段的额外延迟或错误,生产环境通常建议开启skip_name_resolve。
-- 查看用户使用的认证插件 SELECT User, Host, plugin FROM mysql.user WHERE User = 'app_user';
二、请求阶段:逐条SQL的权限校验
连接成功只代表用户通过了身份认证,并不代表用户可以自由执行SQL。每一条SQL语句在解析之后、执行之前,MySQL都会进行一次授权检查。检查依赖的权限数据来自已经加载到内存中的权限表。启动时MySQL会读取mysql.user、mysql.db、mysql.tables_priv、mysql.columns_priv和mysql.procs_priv等表,后续通过GRANT或REVOKE修改权限时会自动更新内存中的副本。如果直接修改权限表,则需要执行FLUSH PRIVILEGES才能让改动生效。
权限检查遵循先全局后细化的顺序。MySQL先检查用户是否拥有全局权限,例如如果账号在mysql.user表中拥有全局SELECT权限,那么该用户可以读取所有数据库中的表。如果没有全局权限,MySQL会继续检查库级权限,也就是mysql.db表。之后还会继续检查表级权限和列级权限。对于存储过程或函数,则检查mysql.procs_priv表。这种逐层细化机制让管理员可以只授予应用账号必要的权限,避免权限过大。
实际开发中,常见的ERROR 1142就发生在请求阶段。例如用户只拥有app_db.orders表的SELECT和INSERT权限,却执行了UPDATE语句,MySQL会拒绝该语句。这个错误与账号能否连接无关,而是第二阶段授权范围覆盖不到当前操作。要查看用户具体拥有哪些权限,可以使用SHOW GRANTS语句。
-- 授予表级读写权限 GRANT SELECT, INSERT, UPDATE ON app_db.orders TO 'app_user'@'192.168.10.%'; -- 查看最终权限 SHOW GRANTS FOR 'app_user'@'192.168.10.%';
三、权限表结构:从全局到列级的实现
mysql.user表承担两个角色:既保存账号认证所需的信息,也保存全局权限。表中包含User、Host、plugin、authentication_string等认证字段,同时还有Select_priv、Insert_priv、Update_priv、Delete_priv等权限列。这些权限列的类型通常是枚举值,用N或Y表示是否拥有对应权限。MySQL 8之后引入动态权限,很多细粒度权限不再以静态列的形式存在,而是记录在mysql.global_grants表中。
mysql.db表则用于库级授权。它的关键列包括Host、Db和User,通过这三个字段的组合决定用户对某个数据库的权限。如果用户只拥有app_db库的SELECT权限,那么该用户默认可以读取app_db库下的所有表,除非在tables_priv表中存在更细粒度的限制。tables_priv表管理表级权限,columns_priv表管理列级权限,procs_priv表管理存储过程和函数的执行权限。
除了直接查权限基表,MySQL还提供information_schema下的权限视图,例如USER_PRIVILEGES、SCHEMA_PRIVILEGES、TABLE_PRIVILEGES和COLUMN_PRIVILEGES。通过视图查询权限比直接查基表更容易理解,因为视图已经将枚举值转换成了可读的权限名称。对于日常排障,先使用SHOW GRANTS看账号汇总权限,再结合具体权限表定位问题,通常会更高效。
-- 查询库级权限 SELECT Host, Db, User, Select_priv, Insert_priv, Update_priv FROM mysql.db WHERE User = 'app_user';
四、认证与授权排障:先看阶段,再看粒度
排查访问控制问题时,第一步是区分错误发生在连接阶段还是请求阶段。ERROR 1045通常表示连接认证失败,需要检查账号是否存在、主机是否匹配、密码是否正确、认证插件是否兼容以及账户是否被锁定。ERROR 1142或ERROR 1044则说明连接已经建立,但当前账号没有对应对象或操作权限。把错误码和阶段对应起来,可以大幅减少无效排查。
日常使用中还有几个容易混淆的地方。能连接不代表能操作,USAGE权限只表示允许连接,没有任何实际数据操作权限。直接修改mysql.user表后如果没有刷新权限,内存中的授权信息不会变化。MySQL 8引入角色机制后,角色需要激活才能发挥作用,否则授权不会应用到当前连接。对于不再使用的账号,可以使用ALTER USER ... ACCOUNT LOCK锁定,而不是直接删除,这样可以保留授权配置以便恢复。
从安全角度看,数据库访问控制应当遵循最小权限原则。应用账号只授予目标库、目标表的必要权限,避免使用root账号连接业务服务。对于敏感数据列,可以通过列级权限限制查询范围。同时建议定期审计用户权限,检查是否存在长期未使用的高权限账号,并及时回收不必要的授权。
-- 锁定账号示例 ALTER USER 'app_user'@'192.168.10.%' ACCOUNT LOCK; -- 解锁账号 ALTER USER 'app_user'@'192.168.10.%' ACCOUNT UNLOCK;
MySQL访问控制的核心价值在于把认证与授权分离。连接阶段解决能不能进来的问题,请求阶段解决进来之后能做什么的问题。两个阶段分别对应不同的权限表和匹配规则,理解这一点后,无论是设计账号体系还是排查权限异常,都可以从更清晰的视角定位问题。实际应用中,应结合业务需求选择全局、库级、表级甚至列级权限,并通过SHOW GRANTS定期确认账号的真实权限范围。