做过读写分离的项目多少都遇到过这样的困惑:明明只想让某个业务账号在从库上做查询,结果这个账号拿到主库上也能登录;反过来,想在从库上单独建一个报表账号,结果权限设置根本不生效。问题的根源在于MySQL的主从复制会把mysql系统库里的权限表也一并同步,导致主从两端的账号权限几乎完全一致。这篇文章就来聊聊如何打破这种"一刀切"的权限模式,让主从节点各司其职。

一、先搞清楚:为什么从库上的授权会失效或被覆盖
MySQL的主从复制本质上是把主库binlog中的事件在从库重放一遍。当你执行GRANT或CREATE USER语句时,这些DDL操作同样会写入binlog,然后被复制到从库执行。也就是说,权限变更和业务数据的变更走的是同一条通道。
这就带来两个典型现象:第一,你在从库上直接执行GRANT,语句本身可以执行成功,但一旦主库有新的权限变更同步过来,或者你重建了复制,从库本地的权限数据就可能和主库不一致,甚至被主库的内容覆盖;第二,从库默认是可写的,如果账号有SUPER权限,即使设置了read_only也能照常写入,造成主从数据不一致。理解了这一点,才能对症下药。
需要注意,从MySQL 8.0开始,GRANT语句不允许隐式创建用户,必须先用CREATE USER建号再授权,写脚本时别把两个版本的行为搞混了。
二、方案一:用read_only加super_read_only做节点级隔离
最简单也最推荐的做法,是不去动权限表,而是给从库加只读约束。这样即使业务账号的权限在主从两端一致,从库也天然拒绝写入,等于在节点层面实现了差异化。
-- 在从库上执行 SET GLOBAL read_only = ON; SET GLOBAL super_read_only = ON; -- 顺便阻止普通用户修改,只留管理员可改 -- 写入my.cnf的[mysqld]段让它重启后依然生效: -- read_only = ON -- super_read_only = ON
这里有个关键细节:super_read_only是在5.7.8引入的。只开read_only时,拥有SUPER权限的账号依然可以写从库,这在很多线上事故里都出现过——开发拿着管理员账号误连从库写数据,复制直接报错断开。开了super_read_only之后,连SUPER用户也会被拒绝写入,安全性大幅提升。
这个方案的优点是配置成本极低,不破坏复制链路;缺点是它只区分了读写能力,没法让两个节点拥有完全不同的账号集合。如果你的需求只是"主库可写、从库只读",用这套配置就够了。
三、方案二:过滤mysql系统库的复制,让从库权限独立
如果确实需要在从库上维护一套独立的账号权限,比如从库上要给数据分析团队开一批主库上不存在的账号,可以考虑在复制配置中排除权限相关的系统表。
在从库的my.cnf中添加:
[mysqld] # 排除掉整个mysql系统库的复制(按需选择其中一种方式) replicate-ignore-db = mysql # 更精细的做法:只排除权限相关的表 # replicate-ignore-table = mysql.user # replicate-ignore-table = mysql.db # replicate-ignore-table = mysql.tables_priv # replicate-ignore-table = mysql.global_priv -- 8.0中权限存储在此表
这样配置之后,主库上的权限变更不会再同步到从库,你可以在从库上自由地CREATE USER和GRANT,两边互不干扰。但这个方案的代价也很明显:一是主从的权限数据永久性分叉,后续做主从切换、故障恢复时很容易出现权限混乱,切换前必须人工核对;二是使用GTID复制时,被过滤的事件仍会占用GTID,虽然8.0通过SET @@SESSION.sql_log_bin=0配合空事务的方式可以处理,但运维复杂度确实上升了。
另一个更优雅的替代思路是:在主库执行权限变更时,临时关闭当前会话的binlog记录,让变更只落在主库本地:
-- 在主库执行,只影响当前会话,不写入binlog SET SESSION sql_log_bin = 0; CREATE USER 'report'@'192.168.1.%' IDENTIFIED BY 'StrongPass#123'; GRANT SELECT ON analytics.* TO 'report'@'192.168.1.%'; SET SESSION sql_log_bin = 1;
反过来,如果你想在从库上建号又怕将来被覆盖,同样的语句在从库上执行也可以,因为从库本地执行的语句默认不会写自己的binlog(除非开了log_slave_updates)。这两种方式比修改复制过滤规则灵活得多,属于临时性差异化授权的首选。
四、方案三:架构层面做账号分流
当集群规模变大、节点不止一主一从时,逐台改配置就不现实了,更通用的做法是把权限差异这件事从数据库层挪到访问层。常见做法有三种。
第一种是使用ProxySQL或MySQL Router做读写分离,不同业务系统对接代理的不同端口或用户,代理根据规则把写请求转发到主库、读请求转发到从库。账号权限仍然统一在主库管理,但业务侧感知不到节点差异。ProxySQL甚至还支持在代理层做账号映射,后端连接可以用统一的账号,前端暴露给业务的账号各不相同。
第二种是网络层隔离:主库只对写入型业务的网段开放3306端口,从库只对查询型业务开放,配合防火墙规则或安全组实现。这种方式粗暴但有效,尤其适合私有化部署的场景。
第三种是给复制专用账号做最小化授权,这也是很多文档里强调但经常被忽视的点。复制账号只需要REPLICATION SLAVE这一个权限:
-- 在主库上创建复制专用账号,权限越小越安全 CREATE USER 'repl'@'192.168.1.%' IDENTIFIED BY 'ReplPass#456'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.1.%';
千万别图省事给复制账号ALL PRIVILEGES,一旦这个账号泄露,攻击者可以拉取你整个库的数据。
五、落地时的几个注意事项
第一,无论选哪种方案,都要想清楚主从切换时权限是否一致。如果用了复制过滤或sql_log_bin=0做过本地授权,切换前务必检查mysql.user表在两端是否同步,否则切换后部分账号会登录失败。
第二,定期用下面的语句审计账号权限,重点比对主从两端的输出差异:
-- 查看所有用户及其认证方式 SELECT user, host, plugin FROM mysql.user; -- 查看具体账号的权限 SHOW GRANTS FOR 'report'@'192.168.1.%';
第三,8.0之后建议用SHOW GRANTS配合角色(ROLE)来管理权限,把权限先赋给角色,再把角色赋给用户,主从架构下的权限调整会清晰很多。总之,差异化授权没有万能解,read_only适合大多数读写分离场景,sql_log_bin适合临时本地授权,代理分流适合大规模集群,按需组合才是正解。
MySQL主从复制主从架构权限REPL slave权限修改时间:2026-09-07 05:22:36