mysql权限需精确指定层级、host和字段,避免误授全局权限或因匹配失败导致失效;grant/revoke自动刷新权限,无需flush privileges;细粒度授权前须厘清账号来源、连接host、访问范围及冲突账号。

GRANT 语句里不写 ON db.table 就会变成全局权限
很多人执行 GRANT SELECT ON *.* 或漏掉 ON 子句,结果误授了整个实例的权限。MySQL 的权限层级是分层的:全局(*.*)、数据库(db.*)、表(db.table)、列((col1,col2)),少写一个层级,权限就自动上浮。
实操建议:
- 明确权限作用域再写语句,比如只给订单表读写:用
GRANT SELECT,INSERT,UPDATE ON shop.orders TO 'app_user'@'10.20.%' - 避免用
GRANT ... ON *.*,除非真要管理账号;生产环境应严格限定到具体库表 - 列级权限必须显式列出字段,如
GRANT UPDATE (status,updated_at) ON shop.orders TO 'app_user'@'10.20.%',不能写UPDATE (*) - 执行后务必用
SHOW GRANTS FOR 'app_user'@'10.20.%'确认实际生效的权限层级
REVOKE 无法撤销隐式继承的权限
MySQL 不支持“撤销某一层级的权限后保留子层级”,比如你先给了 SELECT ON shop.*,又单独给 SELECT ON shop.orders,之后 REVOKE SELECT ON shop.*,那个 shop.orders 的权限并不会保留——它依赖父级授权,一并失效了。
常见错误现象:REVOKE 后应用突然报 ERROR 1142 (42000): SELECT command denied,但明明记得刚授过表级权限。
实操建议:
- 不要混用不同层级的同类型权限,比如既授
shop.*又授shop.orders;优先直接授最细粒度(表或列) - 若需动态收权,先
SHOW GRANTS查清当前所有显式授权,再针对性REVOKE,别依赖“覆盖逻辑” - 列级
REVOKE必须和GRANT字段完全一致,REVOKE UPDATE (status) ON shop.orders不会影响updated_at字段权限
用户 host 匹配不精确导致权限不生效
MySQL 权限记录是按 'user'@'host' 元组匹配的,'app_user'@'%' 和 'app_user'@'10.20.%' 是两条独立记录,不会合并或降级匹配。连接时用的 host 字符串必须和授权记录完全一致(包括通配符位置),否则权限为零。
使用场景:应用部署在 K8s 内网,IP 段是 10.20.5.0/24,但授权写了 'app_user'@'10.20.%',结果部分 Pod 因 DNS 解析出带域名的 host 而连不上。
实操建议:
- 用
SELECT User,Host FROM mysql.user WHERE User='app_user';确认实际存在的账号记录 - 连接时用
mysql -u app_user -h db-host -e "SELECT USER();"看 MySQL 看到的 host 是什么 - 生产环境慎用
'%',优先用 CIDR 段(如'10.20.5.%')或具体 IP;DNS 名需确保反向解析稳定 - host 部分区分大小写,
'app_user'@'DB-SERVER'≠'app_user'@'db-server'
FLUSH PRIVILEGES 不是万能刷新开关
很多文档说改完权限要执行 FLUSH PRIVILEGES,但它只刷新内存中的权限缓存,对已存在的连接无效。新连接才会加载最新权限,而旧连接仍按连接建立时的权限运行——这意味着你 REVOKE 之后,正在跑的定时任务可能还在偷偷删数据。
性能影响:频繁执行 FLUSH PRIVILEGES 会锁住权限系统表,高并发下可能阻塞新连接建立。
实操建议:
-
GRANT/REVOKE本身就会自动重载权限,99% 场景下不需要手动FLUSH - 真正需要
FLUSH的情况极少,仅限直接修改mysql.user等系统表后(不推荐) - 验证权限是否生效,应该新建连接测试,而不是在原会话里反复
FLUSH - 紧急收权时,除了
REVOKE,还得主动 kill 掉可疑的长连接:KILL CONNECTION xxx;
细粒度权限真正难的不是语法,而是每次授权前得想清楚:这个账号到底从哪来、连进来时 MySQL 认它是谁、它接下来要碰哪些字段、有没有其他同名账号干扰匹配。漏掉任意一环,权限就变成摆设。











