应严格遵循最小权限原则,显式授予具体库表的必要权限,避免使用all privileges和.;注意mysql版本差异、grant option残留、跨库join需分别授权、host匹配精度及连接池缓存影响。

GRANT 时别直接给 ALL PRIVILEGES,哪怕只是临时测试
很多 DBA 或开发在建测试账号时随手写 GRANT ALL PRIVILEGES ON *.* TO 'test'@'%',结果发现权限收不回来——REVOKE 对 ALL PRIVILEGES 的撤销行为在 MySQL 8.0+ 和旧版本逻辑不同,且可能漏掉 GRANT OPTION 或列级权限。更麻烦的是,一旦该账号被误授了 PROXY、CREATE USER 等高危权限,后续 REVOKE 不会自动清理这些隐式能力。
实操建议:
- 始终按最小权限原则,显式列出需要的权限,比如
SELECT、INSERT、UPDATE(仅限具体库表) - 避免用
*.*,优先写成`myapp_db`.*或`myapp_db`.`orders` - 如果真要批量授权,用
SHOW GRANTS FOR 'user'@'host'先确认当前权限快照,再比对变更 - MySQL 8.0+ 中,
ALL PRIVILEGES不再包含APPLICATION_PASSWORD_ADMIN等新权限,但旧版本升级后容易误判
REVOKE 失效?先查清权限层级和 WITH GRANT OPTION 是否残留
REVOKE SELECT ON `sales`.* FROM 'analyst'@'10.0.2.%' 执行成功,但用户仍能查数据——大概率是权限来自更高层级(比如 GRANT SELECT ON *.*),或该用户曾被授予 WITH GRANT OPTION 并转授给了别人,而你只 revoke 了原始账号。
实操建议:
- 用
SELECT * FROM mysql.role_edges(MySQL 8.0+)或SELECT * FROM mysql.user WHERE User='xxx'检查是否启用了角色继承 - 运行
SHOW GRANTS FOR 'analyst'@'10.0.2.%',注意输出里是否含GRANT OPTION;若有,必须先REVOKE GRANT OPTION ON ...再 revoke 具体权限 - 权限生效有缓存:MySQL 5.7+ 默认启用
flush privileges自动刷新,但若手动改过mysql.user表,必须显式执行FLUSH PRIVILEGES - PostgreSQL 用户注意:
REVOKE不支持CASCADE默认行为,需加RESTRICT或CASCADE显式声明
跨库查询权限不够?不是漏授 SELECT,而是没开 SELECT INTO OUTFILE 或函数权限
用户能连上、能查本库表,但一执行 SELECT * FROM db1.t1 JOIN db2.t2 就报 ERROR 1142 (42000): SELECT command denied to user——这不是权限没给全,而是 MySQL 默认把跨库 JOIN 视为对“所有涉及库”的独立访问请求,每个库都得单独授权。
实操建议:
- 必须分别执行:
GRANT SELECT ON `db1`.* TO 'user'@'host'和GRANT SELECT ON `db2`.* TO 'user'@'host' - 如果语句里调用了自定义函数或存储过程,还需额外
GRANT EXECUTE ON FUNCTION `db1`.`my_func` - 使用视图时,权限检查发生在视图创建者(
DEFINER)或调用者(SQL SECURITY)层面,容易误以为授了表权限就万事大吉 - MySQL 8.0.16+ 支持
READ_ONLY_ROUTINE权限控制函数调用,但默认不启用,别指望它自动兜底
权限变更后应用连不上?重点盯 max_connections 和 host 匹配精度
明明 GRANT 成功、SHOW GRANTS 也对得上,但应用启动时报 Access denied for user 'app'@'192.168.1.100'——八成是 host 字段匹配失败。MySQL 权限系统严格按 User+Host 组合查表,'app'@'localhost' 和 'app'@'127.0.0.1' 完全是两个账号。
实操建议:
- 确认连接来源 IP,用
SELECT USER(), CURRENT_USER()在会话里看实际匹配到哪个账号 - 避免用
'%'通配符替代具体网段,尤其在多网卡或容器环境里,'app'@'10.0.2.%'比'app'@'%'更安全且不易冲突 - 权限变更后,某些客户端(如 PHP PDO)会复用连接池里的旧连接,导致新权限不生效,可临时设
wait_timeout=10快速验证 - 注意
max_connections是全局变量,但每个账号还能单独设MAX_CONNECTIONS_PER_HOUR,这个限制在GRANT语句里容易被忽略
权限这事,细节全在 Host 字符串、权限层级嵌套、以及不同 MySQL 版本对 GRANT OPTION 的处理差异里。越想“一劳永逸”地批量授权,越容易掉进撤销不了、查不到、连不上的三重坑。










