只读账号必须显式撤销insert/update/delete等写权限及super、lock tables等高危权限,仅授select不生效;需分步create user、grant select、revoke残留权限、flush privileges,并用新连接验证。

只读账号不是“设完 SELECT 就完事”,而是必须显式控制权限范围、撤销残留权限、并适配 MySQL 5.7 的语法与行为边界——否则极易被绕过或报错。
GRANT SELECT 必须配合 REVOKE 才真正只读
MySQL 权限模型是白名单,GRANT SELECT 只加权限,不删任何已有权限。如果用户之前被授过 ALL PRIVILEGES 或属于高权限角色(比如 mysql.session),仅执行 GRANT SELECT 完全无效。
- 先查清当前权限:
SHOW GRANTS FOR 'ro_user'@'%'; - 逐项撤销写类权限:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES, EXECUTE, TRUNCATE, CREATE TEMPORARY TABLES ON `mydb`.* FROM 'ro_user'@'%'; - 检查全局权限:
SELECT User, Host, Super_priv FROM mysql.user WHERE User = 'ro_user';,若Super_priv为 Y,必须用REVOKE SUPER ON *.* FROM 'ro_user'@'%';撤销 - 角色叠加也要处理:若用户已绑定角色,需
REVOKE 'role_name' FROM 'ro_user'@'%';或确认该角色本身只含SELECT
权限范围漏一层,就等于留后门
MySQL 权限按层级生效:全局(*.*)、库级(db.*)、表级(db.table)、列级(db.table(col))。只在库级授 SELECT,不代表表级自动继承——但反过来说,如果某张表单独被 REVOKE SELECT 过,它就会变成不可读。
- 优先用库级授权:
GRANT SELECT ON `reporting_db`.* TO 'bi_reader'@'10.20.%'; - 避免
GRANT SELECT ON *.*:它会暴露mysql库,用户可查mysql.user.authentication_string(密码哈希) - 如需查表结构(
SHOW CREATE TABLE),必须额外授权:GRANT SELECT ON INFORMATION_SCHEMA.* TO 'ro_user'@'%'; - 分区表、临时表、视图依赖的底层表,都要单独验证权限是否覆盖
SELECT FOR UPDATE 会失败,这不是权限配置错误
即使用户只有 SELECT 权限,执行 SELECT ... FOR UPDATE 仍会报错:ERROR 1142 (42000): SELECT command denied。这不是权限漏了,而是 FOR UPDATE 触发了行锁机制,需要 LOCK TABLES 权限——而你刚把它 REVOKE 了。
- BI 工具或 ORM 默认可能带
FOR UPDATE(如 Django 的select_for_update()),需关掉或改用普通SELECT - 应用层应明确区分“只读查询”和“准备更新”的语句路径,避免在只读账号连接里混用
- 不要试图通过授予
LOCK TABLES来“修复”这个错误——那等于开了写入口
CREATE USER 和 GRANT 必须分两步写
MySQL 5.7 不支持在一条语句中同时创建用户并授权。写成 CREATE USER ... GRANT SELECT ... 会直接报语法错误。
- 正确顺序:
CREATE USER 'ro_user'@'192.168.10.%' IDENTIFIED BY 'StrongPass123!';→GRANT SELECT ON `app_logs`.* TO 'ro_user'@'192.168.10.%';→FLUSH PRIVILEGES; -
FLUSH PRIVILEGES在多数场景下非必需(权限缓存在内存中,新连接自动加载),但如果你刚改过mysql.user表或不确定连接池是否复用旧权限,执行它更稳妥 - 验证时务必用新连接登录:
mysql -u ro_user -p -h your-host,别在 root 会话里SET ROLE测试——那不反映真实权限
最常被忽略的是角色继承和 super_read_only 的干扰:如果这个只读账号将来要连到一个启用了 super_read_only = ON 的从库,它不会报错;但如果连到主库且主库也误开了 read_only,它反而会因权限不足无法执行合法 SELECT(比如某些系统表查询)。所以账号权限和实例只读模式得分开设计、各自验证。











