能,但必须显式撤销所有非SELECT权限并检查层级、角色和会话缓存,否则可能因旧权限、全局权限、角色叠加或未重连导致绕过。

MySQL 用户只读权限到底能不能靠 GRANT SELECT 实现
能,但必须同时撤销所有其他权限,否则用户可能通过其他方式绕过限制。MySQL 的权限模型是“显式授予”,不是“默认拒绝”,GRANT SELECT 只加权限,不删权限。
常见错误现象:GRANT SELECT ON db.* TO 'ro_user'@'%'; 执行后,用户仍能 DROP TABLE 或 INSERT——因为旧账号之前被授予过 ALL PRIVILEGES,或者被赋予了角色(如 mysql.session)间接带出高权限。
- 先用
SHOW GRANTS FOR 'ro_user'@'%';确认当前所有权限,别猜 - 务必搭配
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES, EXECUTE ON db.* FROM 'ro_user'@'%'; - 如果用户有全局权限(比如
USAGE以外的GRANT OPTION),也得单独 revoke,否则可能越权授权给他人
只读权限要覆盖哪些对象层级才真正安全
MySQL 权限按层级生效:全局(*)、库级(db.*)、表级(db.table)、列级(db.table(col))。只读不是“设一次就完事”,漏掉某一层就等于留后门。
使用场景:多租户 SaaS 中为客户数据库配只读账号;BI 工具连接数仓时禁止写操作;备份脚本账户需最小权限。
- 优先在库级授权:
GRANT SELECT ON `myapp_prod`.* TO 'bi_reader'@'10.20.%'; - 避免用
GRANT SELECT ON *.*—— 这会包含mysql系统库,用户可查mysql.user密码哈希(5.7+ 是authentication_string字段) - 如果业务明确只要某张表,用表级更细:
GRANT SELECT ON `logdb`.`events_2024` TO 'log_reader'@'%';,但注意分区表、临时表、视图依赖的底层表也要检查权限
REVOKE 后为什么 SHOW GRANTS 还显示旧权限
权限变更不会实时刷新到已存在的连接,这是 MySQL 的设计行为。用户当前会话仍保留旧权限,新连接才生效。
错误现象:执行完 REVOKE,立刻用原连接 SELECT 没问题,INSERT 也成功——不是没 revoke 成功,是会话缓存没更新。
- 强制刷新权限缓存:
FLUSH PRIVILEGES;(仅当直接改了mysql库表才需要;正常用GRANT/REVOKE不必) - 真正有效的方式是让客户端重连,或等连接超时断开
- 验证是否生效:开一个全新连接,再执行
SHOW GRANTS;和测试INSERT INTO ... VALUES(...);,看是否报错ERROR 1142 (42000): INSERT command denied
只读账号还能干啥危险事?容易被忽略的绕过点
SELECT 权限本身不危险,但配合某些功能或配置,可能造成数据泄露、服务干扰甚至提权。
性能影响:大量 SELECT SLEEP(10) 或全表扫描可拖垮查询线程池;兼容性影响:MySQL 8.0+ 引入角色(ROLE)机制,若只读用户被加入含写权限的角色,权限会叠加生效。
-
SELECT能读INFORMATION_SCHEMA,暴露表结构、索引、字符集、甚至触发器定义——敏感字段名、脱敏规则可能被反推 - 若开启
secure_file_priv为空或宽松路径,配合SELECT ... INTO OUTFILE可导出数据到服务器磁盘(虽需 FILE 权限,但有些老部署误开了) - 视图(VIEW)和存储函数(FUNCTION)若定义者是高权限用户且 SQL SECURITY DEFINER,则只读用户调用时可能间接执行写操作——检查
SHOW CREATE VIEW view_name;中的SQL SECURITY设置
最麻烦的其实是权限继承链:角色嵌套、代理用户(PROXY)、组复制(Group Replication)成员自动获得元数据访问权。真要锁死,得从账号来源、认证插件、甚至网络层做隔离。


















