创建只读账号需先revoke all privileges再grant select限定库表,配合password expire与max_connections_per_hour实现软限时,用视图隔离敏感字段,并确保host匹配和ssl配置正确。

创建只读账号时必须显式 REVOKE 写权限
MySQL 默认不自动限制写操作,哪怕只给 SELECT 权限,也要防着隐式继承或历史残留权限。开发人员用的账号一旦被误授 INSERT、UPDATE 或更危险的 DROP,就可能误删测试数据甚至影响结构。
实操建议:
- 用
CREATE USER新建账号后,立刻执行REVOKE ALL PRIVILEGES ON *.* FROM 'dev_readonly'@'%',清空所有默认继承权限 - 再单独
GRANT SELECT ON `project_db`.* TO 'dev_readonly'@'%',限定库名,别用*通配全局 - 检查是否生效:用该账号登录后执行
SHOW GRANTS FOR CURRENT_USER,确认输出里只有SELECT且作用域精确
设置账号过期时间得靠 ALTER USER ... PASSWORD EXPIRE + MAX_CONNECTIONS_PER_HOUR
MySQL 原生不支持“账号在某天自动禁用”,但能通过密码过期+连接频控组合实现“软限时”。单纯设 PASSWORD EXPIRE 不够——开发人员可以立刻改密绕过;只限连接数又无法控制生命周期。
实操建议:
- 创建时加
ALTER USER 'dev_readonly'@'%' PASSWORD EXPIRE INTERVAL 7 DAY,强制一周后需重置(实际是禁止登录) - 同步限制资源:
ALTER USER 'dev_readonly'@'%' WITH MAX_CONNECTIONS_PER_HOUR 10,防脚本暴力扫表 - 如果用的是 MySQL 5.7+,避免用已废弃的
mysql.user表直接 UPDATE,必须走ALTER USER语句,否则权限不刷新
开发环境连不上?大概率是 host 匹配和 SSL 要求冲突
给开发人员开账号时填 '%' 看似方便,但 MySQL 实际按 host 字符串精确匹配,'localhost' 和 '127.0.0.1' 是两个不同用户;另外某些公司 DBA 强制开启 require_secure_transport=ON,而开发本地工具常默认不走 SSL。
实操建议:
- 查清楚开发人员实际连接 IP 或 hostname,用
SELECT user, host FROM mysql.user WHERE user = 'dev_readonly'确认记录存在且 host 对应 - 如果他们用 Navicat / DBeaver 连
localhost,就额外建一个'dev_readonly'@'localhost'并赋同样权限 - 若服务器启用了强制 SSL,要么临时关掉(仅限内网开发环境),要么让开发在连接字符串里加
?ssl-mode=REQUIRED并提供 CA 证书
SELECT 权限不够细?用视图隔离敏感字段比授全表更可靠
有些表含 password_hash、email、phone 等字段,只靠库级 SELECT 权限拦不住——开发一查 SELECT * FROM users 就全暴露了。MySQL 8.0+ 支持列级权限,但维护成本高、兼容性差,不如用视图兜底。
实操建议:
- 建视图时明确剔除敏感列:
CREATE VIEW v_users_public AS SELECT id, username, created_at FROM users - 只对视图授
SELECT:GRANT SELECT ON `project_db`.`v_users_public` TO 'dev_readonly'@'%',别给原表任何权限 - 注意视图依赖表权限:如果
users表本身没被授权,视图会报ERROR 1356 (HY000): View 'project_db.v_users_public' references invalid table(s) or column(s)
复杂点在于权限叠加逻辑和 host 匹配顺序,MySQL 查用户权限时先找最具体的 host 记录,再 fallback 到通配符;而视图和底层表权限是分开校验的,漏掉任意一层都可能越权。











