刚接触MySQL权限管理时,很多人会觉得GRANT和REVOKE命令看起来很简单,但实际操作中却经常遇到各种"诡异"问题。比如明明已经授权了,用户却说没有权限;或者回收了权限,却发现还能继续操作。这些问题背后往往隐藏着MySQL权限系统的精妙设计。
很多新手在创建用户时,对user@localhost和user@%的区别感到困惑。这两者看似只是主机名不同,实际却代表着完全不同的访问控制逻辑。
user@localhost:只允许从MySQL服务器本机通过本地socket连接user@%:允许从任何主机通过TCP/IP连接(除非配置了skip-networking)常见误区:
sql复制-- 错误示范:以为这样就能允许本地和远程连接
GRANT ALL ON db.* TO 'user'@'localhost';
GRANT ALL ON db.* TO 'user'@'%';
实际上,MySQL将这两个视为完全不同的用户账户!它们可以有不同的密码和权限。更合理的做法是:
sql复制-- 正确做法:统一密码
CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';
CREATE USER 'user'@'%' IDENTIFIED BY 'password';
GRANT ALL ON db.* TO 'user'@'localhost';
GRANT ALL ON db.* TO 'user'@'%';
提示:生产环境中,建议限制
@%的使用,只开放必要的远程访问权限。
WITH GRANT OPTION是一个强大的功能,允许被授权用户将自己的权限授予其他用户。但这也是一把双刃剑。
危险场景:
sql复制-- user1可以将权限继续授予user2
GRANT SELECT ON db.* TO 'user1'@'%' WITH GRANT OPTION;
此时,user1可以执行:
sql复制GRANT SELECT ON db.* TO 'user2'@'%';
即使管理员收回了user1的权限:
sql复制REVOKE SELECT ON db.* FROM 'user1'@'%';
user2仍然保留着权限!要彻底清理,必须:
sql复制-- 先收回授予权
REVOKE GRANT OPTION ON db.* FROM 'user1'@'%';
-- 再收回权限
REVOKE SELECT ON db.* FROM 'user1'@'%';
-- 最后检查并清理被授予的权限
SHOW GRANTS FOR 'user2'@'%';
REVOKE SELECT ON db.* FROM 'user2'@'%';
MySQL的权限系统是分层的,包括:
| 权限层级 | 示例 | 影响范围 |
|---|---|---|
| 全局 | GRANT ALL ON *.* |
所有数据库 |
| 数据库 | GRANT ALL ON db.* |
指定数据库的所有对象 |
| 表 | GRANT ALL ON db.table |
指定表 |
| 列 | GRANT SELECT(col) ON db.table |
指定列 |
常见问题:在数据库层级授予权限后,又单独回收表级权限,发现不生效。
sql复制-- 授予数据库所有权限
GRANT ALL ON teachingdb.* TO 'user'@'%';
-- 尝试回收某个表的权限(这不会生效!)
REVOKE SELECT ON teachingdb.student FROM 'user'@'%';
因为数据库层级的ALL权限已经包含表级SELECT权限。正确的做法是先回收高级别权限:
sql复制-- 先回收数据库级权限
REVOKE ALL ON teachingdb.* FROM 'user'@'%';
-- 然后重新授予除student表外的权限
GRANT ALL ON teachingdb.* TO 'user'@'%';
REVOKE ALL ON teachingdb.student FROM 'user'@'%';
关于权限变更何时生效,存在很多误解:
直接修改权限表(不推荐):
sql复制UPDATE mysql.user SET Select_priv='Y' WHERE User='user';
-- 必须执行
FLUSH PRIVILEGES;
使用GRANT/REVOKE语句:
sql复制GRANT SELECT ON db.* TO 'user'@'%';
-- 不需要FLUSH PRIVILEGES,自动生效
例外情况:
修改用户密码时:
sql复制SET PASSWORD FOR 'user'@'%' = PASSWORD('newpass');
-- 不需要FLUSH
sql复制UPDATE mysql.user SET Password=PASSWORD('newpass') WHERE User='user';
-- 需要FLUSH
注意:生产环境中,建议始终使用GRANT/REVOKE/SET PASSWORD等标准语句,避免直接操作权限表。
MySQL检查权限时遵循特定顺序,这可能导致一些"意外"行为:
权限检查顺序:
权限冲突解决原则:
典型场景:
sql复制-- 授予全局SELECT权限
GRANT SELECT ON *.* TO 'user'@'%';
-- 拒绝特定数据库的SELECT
REVOKE SELECT ON sensitive.* FROM 'user'@'%';
-- 但用户仍然能看到sensitive数据库的存在!
要完全隐藏数据库,需要:
sql复制-- 先回收全局权限
REVOKE SELECT ON *.* FROM 'user'@'%';
-- 然后逐个授权非敏感数据库
GRANT SELECT ON db1.* TO 'user'@'%';
GRANT SELECT ON db2.* TO 'user'@'%';
假设我们需要为一个数据分析师配置权限:
sql复制-- 创建用户(两种连接方式)
CREATE USER 'analyst'@'192.168.1.%' IDENTIFIED BY 'securePass123';
CREATE USER 'analyst'@'localhost' IDENTIFIED BY 'securePass123';
-- 授予只读权限给非敏感数据库
GRANT SELECT ON `production`.* TO 'analyst'@'192.168.1.%';
GRANT SELECT ON `reporting`.* TO 'analyst'@'192.168.1.%';
GRANT SELECT ON `production`.* TO 'analyst'@'localhost';
GRANT SELECT ON `reporting`.* TO 'analyst'@'localhost';
-- 授予读写权限给analysis数据库
GRANT ALL ON `analysis`.* TO 'analyst'@'192.168.1.%';
GRANT ALL ON `analysis`.* TO 'analyst'@'localhost';
-- 设置查询限制
GRANT USAGE ON *.* TO 'analyst'@'192.168.1.%'
WITH MAX_QUERIES_PER_HOUR 1000;
GRANT USAGE ON *.* TO 'analyst'@'localhost'
WITH MAX_QUERIES_PER_HOUR 1000;
-- 确保没有WITH GRANT OPTION
SHOW GRANTS FOR 'analyst'@'192.168.1.%';
SHOW GRANTS FOR 'analyst'@'localhost';
SHOW GRANTS检查权限分配sql复制-- 有用的检查命令
-- 查看所有用户
SELECT User, Host FROM mysql.user;
-- 查看用户权限
SHOW GRANTS FOR 'user'@'%';
-- 查看当前权限
SHOW PRIVILEGES;
遇到权限问题时,可以按照以下流程排查: