mysql只读账号需显式回收写权限,否则仍可执行drop等操作;主库创建的账号不会自动同步至从库;权限变更对已存在连接无效,需重启应用或连接池。

只给读账号 SELECT,但没 REVOKE 写权限会出事
MySQL 的 SELECT 权限本身不排斥写操作——它只是“允许查”,不是“禁止改”。如果账号之前被授过 INSERT 或继承了带写权限的角色,哪怕你只执行了 GRANT SELECT,它照样能删表、锁表、建索引。
真实线上事故里,有只读账号执行 DROP DATABASE 导致服务中断,原因就是没显式回收 DROP、ALTER、LOCK TABLES 等权限。这些不是“额外加的”,而是必须主动砍掉的。
- 先用
DROP USER IF EXISTS 'reader'@'%'彻底清旧账号,避免残留权限继承 - 再创建新账号:
CREATE USER 'reader'@'192.168.10.%' IDENTIFIED BY 'pass123'; - 只授读:
GRANT SELECT ON `myapp`.* TO 'reader'@'192.168.10.%'; - 必须补上这句:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES ON `myapp`.* FROM 'reader'@'192.168.10.%'; -
FLUSH PRIVILEGES;不可省,尤其在跳过 grant 表启动或权限表未启用 binlog 复制时
写账号不能带 SUPER,更别碰 REPLICATION SLAVE
SUPER 权限能让应用账号执行 KILL、SET GLOBAL、停掉从库 SQL 线程——这不是业务需求,是运维事故放大器。而 REPLICATION SLAVE 是给从库 I/O 线程用的,不是给人用的;一旦写账号拥有它,就能拉取主库全部 binlog,等于拿到全量数据变更日志,属于高危权限。
很多团队误以为“从库要同步,那账号得有复制权限”,这是混淆了「复制角色」和「应用角色」。
- 写账号应严格限定来源 IP:
'writer'@'192.168.10.50',禁用'%' - 授予权限只到业务所需:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON `myapp`.* TO 'writer'@'192.168.10.50'; - 显式回收危险权限:
REVOKE SUPER, REPLICATION CLIENT, REPLICATION SLAVE ON *.* FROM 'writer'@'192.168.10.50'; - 不授
GRANT OPTION,防止账号自行扩散权限
主库建的账号,从库上根本不存在
MySQL 主从复制默认不复制 mysql 库,所以你在主库执行 CREATE USER,从库不会自动同步该账号。结果就是:应用连主库成功,但读请求路由到从库时直接报 Access denied for user 'reader'@'192.168.10.100'。
这不是网络或密码问题,是账号根本没在从库上创建。线上常见排查盲区是只检查主库权限,忘了登录从库查 SELECT User, Host FROM mysql.user;。
- 必须在每台从库上单独执行相同的
CREATE USER+GRANT+REVOKE语句 - 建议用部署脚本统一初始化,避免人工漏配
- 若用 MySQL 8.0+ 的角色(role),也要在每台从库上
CREATE ROLE并GRANT给对应用户 - 从库上禁止开放
reader账号给任意 IP,主机段限制为应用服务器段即可
权限改了,老连接还是旧权限
MySQL 8.0+ 默认启用权限缓存,FLUSH PRIVILEGES 只更新内存中的权限映射表,已建立的连接仍沿用握手时加载的旧权限,直到断开重连。这不是 bug,是性能设计——避免每次查询都去查 mysql.user 表。
所以你看到“明明刚授了 SELECT,应用还是报拒绝”,大概率是应用连接池没重启,还在用旧连接。
- 验证方式:在从库上执行
SELECT USER(), CURRENT_USER();,确认当前会话用的是哪个账号 - 临时验证可手动
KILL对应连接 ID,触发重连 - 生产环境务必滚动重启应用,或配置连接池最小空闲连接数为 0,让旧连接自然淘汰
- 不要依赖
FLUSH PRIVILEGES后立刻生效,它只影响新连接
权限分配真正难的不是语法,是把“谁在哪台机器上能做什么”这张图理清楚——主库账号 ≠ 从库账号,读账号 ≠ 写账号,新连接 ≠ 老连接。漏掉任一环,读写分离就变成权限裸奔。











