如何设置MySQL临时只读权限,为开发人员创建短期有效的只读账号?
- 内容介绍
- 文章标签
- 相关推荐
本文共计802个文字,预计阅读时间需要4分钟。
MySQL不支持CREATE USER ... IDENTIFIED BY 'pwd' EXPIRE AFTER 7 DAY这类语法(那是MariaDB的特性),也没有内置的READ_ONLY_ROLE可直接分配给用户。所谓临时只读权限,必须人工控制生命周期:
创建带 IP 限制和库级 SELECT 的只读账号
先建用户,限定来源更安全:
CREATE USER 'dev_ro_2026q2'@'10.20.30.%' IDENTIFIED BY 'ro_temp_8xK!';
再授最小权限 —— 注意三点:
- 必须用反引号包裹数据库名,比如
`billing-prod`,否则含短横的库名会报语法错误 - 不能写
GRANT SELECT ON *.*,否则会暴露mysql、information_schema等系统库 - 如果开发只需查一张表(如日志归档表),就精确到表:
GRANT SELECT ON `logdb`.`events_202604` TO 'dev_ro_2026q2'@'10.20.30.%';
执行完记得刷新(MySQL 5.7 或某些容器部署仍依赖它):
FLUSH PRIVILEGES;
必须显式回收高危权限,不能只靠“没授就不算有”
新建用户默认只有 USAGE 权限,看似安全,但以下情况会导致意外越权:
- 该用户名在其他 host 上已有残留记录(比如
'dev_ro_2026q2'@'%'),权限会叠加 - 旧账号被复制同步到从库,而从库开启了
read_only=0,等于开了后门 - 没收回
FILE权限,用户可用SELECT ... INTO OUTFILE写服务器文件 - 漏掉
EXECUTE,若库中有存储过程,可能间接触发写操作
稳妥做法是主动撤销所有非 SELECT 权限:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES, EXECUTE, FILE ON *.* FROM 'dev_ro_2026q2'@'10.20.30.%';
再检查一遍:SHOW GRANTS FOR 'dev_ro_2026q2'@'10.20.30.%'; —— 输出里应该只有 SELECT 行。
验证时别用旧连接,要开新会话测 INSERT 是否真被拒
权限变更对已存在的连接不生效。常见误判是:
- 用 root 改完权限后,立刻用同一个终端切到该用户测试,结果发现 INSERT 居然成功 —— 其实是旧会话缓存了权限
- 忘了删掉同名但不同 host 的冗余账号,比如留着
@'%'记录,导致实际匹配到了更高权限项
正确验证步骤:
- 新开一个终端,用
mysql -u dev_ro_2026q2 -p -h your-db-host连接 - 执行
SELECT DATABASE();确认当前库是否为你授权的目标库 - 执行
INSERT INTO your_target_db.users VALUES ();—— 必须报错ERROR 1142 (42000): INSERT command denied - 执行
SELECT * FROM information_schema.TABLES LIMIT 1;—— 应允许(元数据可读),但SELECT * FROM other_db.users;必须拒绝
真正麻烦的不是建账号,而是后续没人记得这个账号该在哪天删。建议把清理动作写进运维排期,或者用脚本定期扫描 user 表里含 _ro_ 和 _2026q2 这类命名模式的账号并告警。
本文共计802个文字,预计阅读时间需要4分钟。
MySQL不支持CREATE USER ... IDENTIFIED BY 'pwd' EXPIRE AFTER 7 DAY这类语法(那是MariaDB的特性),也没有内置的READ_ONLY_ROLE可直接分配给用户。所谓临时只读权限,必须人工控制生命周期:
创建带 IP 限制和库级 SELECT 的只读账号
先建用户,限定来源更安全:
CREATE USER 'dev_ro_2026q2'@'10.20.30.%' IDENTIFIED BY 'ro_temp_8xK!';
再授最小权限 —— 注意三点:
- 必须用反引号包裹数据库名,比如
`billing-prod`,否则含短横的库名会报语法错误 - 不能写
GRANT SELECT ON *.*,否则会暴露mysql、information_schema等系统库 - 如果开发只需查一张表(如日志归档表),就精确到表:
GRANT SELECT ON `logdb`.`events_202604` TO 'dev_ro_2026q2'@'10.20.30.%';
执行完记得刷新(MySQL 5.7 或某些容器部署仍依赖它):
FLUSH PRIVILEGES;
必须显式回收高危权限,不能只靠“没授就不算有”
新建用户默认只有 USAGE 权限,看似安全,但以下情况会导致意外越权:
- 该用户名在其他 host 上已有残留记录(比如
'dev_ro_2026q2'@'%'),权限会叠加 - 旧账号被复制同步到从库,而从库开启了
read_only=0,等于开了后门 - 没收回
FILE权限,用户可用SELECT ... INTO OUTFILE写服务器文件 - 漏掉
EXECUTE,若库中有存储过程,可能间接触发写操作
稳妥做法是主动撤销所有非 SELECT 权限:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES, EXECUTE, FILE ON *.* FROM 'dev_ro_2026q2'@'10.20.30.%';
再检查一遍:SHOW GRANTS FOR 'dev_ro_2026q2'@'10.20.30.%'; —— 输出里应该只有 SELECT 行。
验证时别用旧连接,要开新会话测 INSERT 是否真被拒
权限变更对已存在的连接不生效。常见误判是:
- 用 root 改完权限后,立刻用同一个终端切到该用户测试,结果发现 INSERT 居然成功 —— 其实是旧会话缓存了权限
- 忘了删掉同名但不同 host 的冗余账号,比如留着
@'%'记录,导致实际匹配到了更高权限项
正确验证步骤:
- 新开一个终端,用
mysql -u dev_ro_2026q2 -p -h your-db-host连接 - 执行
SELECT DATABASE();确认当前库是否为你授权的目标库 - 执行
INSERT INTO your_target_db.users VALUES ();—— 必须报错ERROR 1142 (42000): INSERT command denied - 执行
SELECT * FROM information_schema.TABLES LIMIT 1;—— 应允许(元数据可读),但SELECT * FROM other_db.users;必须拒绝
真正麻烦的不是建账号,而是后续没人记得这个账号该在哪天删。建议把清理动作写进运维排期,或者用脚本定期扫描 user 表里含 _ro_ 和 _2026q2 这类命名模式的账号并告警。

