只授予select权限且显式限定库或表范围是实现mysql只读的唯一可靠方式;必须避免grant select on .、清理冗余写权限、禁用information_schema和show databases访问,并通过实际登录验证。

直接结论:只授予 SELECT 权限,且必须显式指定数据库和表范围;任何遗漏、宽泛或残留的权限都会让“只读”失效。
GRANT SELECT 必须精确到库或表,不能偷懒写 *.*
MySQL 的权限模型是“默认拒绝”,但一旦你执行了 GRANT SELECT ON *.*,用户就能查所有库——包括 mysql、performance_schema 等系统库,极易泄露结构或敏感配置。
- ✅ 正确做法:按需授权,例如
GRANT SELECT ON `app_db`.* TO 'reporter'@'10.0.2.%';或更细粒度的GRANT SELECT ON `app_db`.`orders` TO 'reporter'@'%'; - ❌ 常见错误:用
GRANT SELECT ON *.*后再试图靠REVOKE清理——它不覆盖已存在的库/表级授权,反而容易漏掉 - ⚠️ 注意通配符:
app_db.%不是“app_db 下所有表”,而是匹配所有以app_db_开头的库名(比如app_db_backup),别被名字骗了
必须清理已有冗余权限,尤其是 INSERT/UPDATE/DELETE
很多只读用户出问题,不是因为没授 SELECT,而是旧账号早就有写权限,新 GRANT 并不会自动覆盖或清除它们。
- 先查真实权限:
SHOW GRANTS FOR 'reporter'@'10.0.2.%';—— 别信记忆或文档,只信这条命令输出 - 逐条回收:例如
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER ON `app_db`.* FROM 'reporter'@'10.0.2.%'; - 特别注意:
USAGE权限只是允许连接,不等于无权;但它常和其他权限共存,容易忽略
INFORMATION_SCHEMA 和 SHOW DATABASES 是“只读漏洞”高发区
即使用户只有 SELECT 权限,也能访问 INFORMATION_SCHEMA.TABLES 查到其他库名、表结构,甚至通过 SELECT COUNT(*) FROM other_db.sensitive_table 推断数据存在性。
- MySQL 8.0.12+ 可禁用元数据访问:
REVOKE SELECT ON `INFORMATION_SCHEMA`.* FROM 'reporter'@'10.0.2.%'; - 关闭
SHOW DATABASES权限(默认不授予,但若误授过需回收):REVOKE SHOW DATABASES ON *.* FROM 'reporter'@'10.0.2.%'; - 敏感字段隔离建议用视图:
CREATE VIEW safe_orders AS SELECT id, amount, status FROM orders;,再授SELECT给视图,比列级授权更可靠
验证必须用实际账号登录测试,不能只看 SHOW GRANTS
SHOW GRANTS 输出看起来干净,不代表权限真正生效——可能 FLUSH PRIVILEGES 没执行,也可能权限被更高级别的角色覆盖(尤其在启用 roles 的 MySQL 8.0+ 环境中)。
- 用新账号连上去实测:
mysql -u reporter -p -h db-host - 成功:执行
SELECT COUNT(*) FROM app_db.orders;返回数字 - 失败:执行
INSERT INTO app_db.orders (amount) VALUES (100);必须报错ERROR 1142 (42000): INSERT command denied - 关键细节:某些看似只读的操作会隐式触发写行为,比如
SELECT ... FOR UPDATE或调用含写逻辑的存储函数,这些权限不在SELECT范围内,但需提前识别业务 SQL 是否含这类语句
最容易被忽略的是权限继承和角色叠加——如果用户属于某个 role,而该 role 被授予了写权限,单靠回收用户直连权限是无效的。生产环境务必检查 SELECT * FROM mysql.role_edges; 和 SHOW GRANTS FOR ROLE 'xxx';。











