多实例权限隔离必须依赖host字段而非仅用户名,同一用户在不同host下为独立账号;需按应用IP或域名精确创建账号,读写分离场景下主从库账号权限须严格分离,禁用GRANT OPTION,权限变更后需主动清理旧连接。

多实例间权限隔离必须靠 host 字段,不是只靠用户名
MySQL 的用户身份由 user@host 共同决定,同一用户名在不同 host 下是完全独立的账号。集群里常见错误是只建了 'app'@'%',结果所有节点都走这个通配账号,权限收不紧,也分不清谁在调用。
- 应用 A(10.10.1.10)和应用 B(10.10.2.20)必须分别创建
'app_a'@'10.10.1.10'和'app_b'@'10.10.2.20',不能共用'app'@'%' -
host写成'%'在测试环境可以,上线后必须收敛——尤其注意容器或云环境的 IP 可能是动态的,这时改用'app_a'@'10.10.1.%'或反向 DNS 域名(如'app_a'@'svc-a.internal')更可控 - 执行
CREATE USER后务必立刻FLUSH PRIVILEGES,否则权限不生效;但如果是用GRANT创建用户(5.7+),这步可省
读写分离场景下,SELECT 和 INSERT/UPDATE/DELETE 权限要拆到不同账号
主库只给写权限、从库只给读权限,不是靠应用层控制,而是靠账号本身权限隔离。一旦某个账号在从库上也有 UPDATE 权限,出问题时排查成本陡增。
- 主库账号(如
'app_rw'@'10.10.1.10')只授SELECT, INSERT, UPDATE, DELETE, EXECUTE,禁用CREATE、DROP、ALTER - 从库账号(如
'app_ro'@'10.10.1.10')只授SELECT,且显式REVOKE掉其他权限(避免继承) - 注意:如果应用用了连接池并复用连接,它可能在从库连接上发写请求——这时 MySQL 会直接报错
ERROR 1290 (HY000): The MySQL server is running with the --read-only option,但这属于运行时拦截,不是权限控制,不能替代账号级限制
GRANT OPTION 是高危权限,集群中应全局禁用
只要一个账号有 GRANT OPTION,它就能把自己没权限的表授权给别人,或者把更高权限转授出去。在多团队协作的集群里,等于开了个权限后门。
- 检查现有账号:执行
SELECT user,host,grant_priv FROM mysql.user WHERE grant_priv = 'Y';,发现就立刻REVOKE GRANT OPTION ON *.* FROM 'xxx'@'yyy'; - 初始化账号时,永远不要加
WITH GRANT OPTION;哪怕 DBA 自己用的管理账号,也建议单独开一个带该权限的账号,且仅限跳板机 IP 登录 - 某些 ORM(如 Django 的
syncdb或 Alembic 升级)会尝试建临时表或视图,误触发对GRANT OPTION的依赖——这不是必须的,关掉对应功能或改用专用迁移账号即可
权限变更后,旧连接不会自动失效,得靠连接空闲超时或主动踢掉
执行 GRANT 或 REVOKE 后,已存在的连接仍维持原权限,直到断开重连。这意味着权限收紧后,你可能以为生效了,其实老连接还在“裸奔”。
- 查看活跃连接:用
SELECT id,user,host,command,time,state FROM information_schema.processlist WHERE user LIKE 'app%'; - 强制刷新:对关键账号,可用
KILL命令干掉旧连接(如KILL 12345;),但生产环境慎用;更稳妥的是设置短连接空闲超时:wait_timeout=60(单位秒),让闲置连接自动断开 - 应用层配合:连接池配置里打开
testOnBorrow或validationQuery=SELECT 1,确保每次取连接前验证有效性,也能间接触发权限重载
权限不是设完就一劳永逸的事,真正难的是持续收敛——比如新服务上线时顺手开了 '%' ,三个月后忘了清理;或者某次紧急修复给了临时 ALL PRIVILEGES,之后没人回收。这些细节比方案本身更容易出问题。











