最直接有效方式是只授SELECT权限并显式回收所有DML/DDL权限:GRANT SELECT ON myapp.users TO 'reporter'@'10.20.30.%';REVOKE INSERT,UPDATE,DELETE,DROP,CREATE,ALTER ON myapp.* FROM 'reporter'@'10.20.30.%';禁用SHOW DATABASES和INFORMATION_SCHEMA访问;应用层必须加LIMIT防全表扫描。

只给 SELECT 权限,同时清理掉所有其他 DML 和 DDL 权限,是限制用户执行 INSERT/UPDATE/DELETE 的最直接有效方式。
GRANT SELECT 时必须精确指定库表,不能用 *.*
MySQL 不支持“仅允许 SELECT”的通配式授权。写 GRANT SELECT ON *.* TO 'user'@'host' 会把所有库所有表的 SELECT 权限都打开,包括 mysql、information_schema 等系统库——这等于开了后门。
正确做法是明确到具体库和表:
GRANT SELECT ON myapp.users TO 'reporter'@'10.20.30.%'GRANT SELECT ON myapp.orders TO 'reporter'@'10.20.30.%'- 如果要覆盖整库,用
myapp.*,但需确认该库下没有敏感元数据表(如日志表、配置表)
执行完务必运行 FLUSH PRIVILEGES;,否则权限不生效。
REVOKE 要显式清除已有 DML 权限
很多用户以为“没给 INSERT 就不能插”,但实际中该账号可能早先被授予过 ALL PRIVILEGES 或 INSERT, UPDATE, DELETE,这些权限不会因新授 SELECT 而自动失效。
必须主动回收:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER ON myapp.* FROM 'reporter'@'10.20.30.%'- 若用户有全局权限(如
INSERT ON *.*),也要一并REVOKE,否则仍可跨库操作 - 检查是否残留
SUPER或PROCESS:这类高危权限能绕过多数 DML 限制,生产环境应彻底禁用
禁止跨库访问和元数据窥探
即使只给了 myapp.* 的 SELECT,用户仍可能通过 SHOW DATABASES 或查询 information_schema 探测其他库结构,甚至构造跨库查询(如 SELECT * FROM otherdb.config)。
加固要点:
- 不授予
SHOW DATABASES权限——这样SHOW DATABASES命令返回空,用户连其他库名都看不到 - MySQL 8.0+ 可执行
REVOKE SELECT ON INFORMATION_SCHEMA.* FROM 'reporter'@'10.20.30.%';5.7 不支持该粒度,只能靠应用层过滤或视图封装 - 创建安全视图(如
CREATE VIEW safe_users AS SELECT id, name FROM users),再只对视图授 SELECT,隐藏原始表结构和冗余字段
应用层加 LIMIT 是防全表扫描的最后防线
MySQL 没有服务端强制的“单次查询最多返回 N 行”机制。max_rows 变量只影响客户端显示,服务端照样返回全部结果,网络和内存压力照旧。
真正可控的方式只有两种:
- 在应用代码里,所有查询语句末尾统一拼接
LIMIT 1000(或按业务设合理上限) - 用代理层(如 ProxySQL、MaxScale)重写 SQL,在转发前自动注入 LIMIT;但需注意
ORDER BY+LIMIT组合可能导致非预期截断,建议搭配WHERE条件使用
别指望靠权限控制住大结果集——它只管“能不能查”,不管“查多少”。这点容易被忽略,直到某次慢查询拖垮数据库连接池。











