可行,但必须显式拒绝SELECT权限:新建用户默认无任何权限,只授INSERT需精确到库表、禁用宽泛授权、验证SELECT报错且INSERT成功,并排查角色继承、INFORMATION_SCHEMA授权等隐式风险。

只给INSERT不给SELECT是可行的,但必须显式拒绝SELECT
MySQL默认不授予任何权限,所以新建用户天然不能SELECT也不能INSERT。只开INSERT、关SELECT,关键不是“不给SELECT”,而是确保SELECT权限没被意外授予——哪怕一次GRANT ALL PRIVILEGES或GRANT SELECT ON *.*都会破防。
常见错误是以为“没给SELECT就等于不能查”,结果发现用户能查,原因往往是:之前执行过宽泛授权、用USAGE创建用户后又误授了全局权限、或者该用户被赋予了角色间接继承了SELECT。
- 创建用户时别用
GRANT ALL PRIVILEGES,哪怕临时测试也不行 - 必须显式执行
REVOKE SELECT ON *.* FROM 'user'@'host'(如果之前有) -
SHOW GRANTS FOR 'user'@'host'输出里绝对不能出现SELECT字样 - 若用户已存在且权限混乱,建议先
DROP USER再重建,比清理更可靠
GRANT INSERT时必须指定库和表,不能省略范围
GRANT INSERT本身不带隐含读取能力,但它必须绑定到具体对象,否则无效。写成GRANT INSERT ON *.* TO 'writer'@'%'看似省事,实则危险——它开了所有库的INSERT,还可能因MySQL版本或配置意外暴露系统表。
真正安全的做法是精确到库+表,例如只允许向logs.events插入:
GRANT INSERT ON `logs`.`events` TO 'writer'@'10.0.2.%';
注意反引号包裹库名和表名,避免特殊字符或关键字冲突;host部分尽量用IP段(如'10.0.2.%')而非'%'。
- 不要用
GRANT INSERT ON logs.*——除非确认该库下所有表都允许插入 - 如果业务只要往一张表写,就只授那张表,别图省事授整个库
- 授完立刻
FLUSH PRIVILEGES(尤其在MySQL 5.7或某些容器化部署中,权限不会自动刷新)
验证INSERT可用但SELECT报错的最简方式
别只靠SHOW GRANTS判断,得真连进去试。用新用户登录后,执行两条命令就能闭环验证:
-
INSERT INTO logs.events (msg, ts) VALUES ('test', NOW());→ 应成功返回Query OK, 1 row affected -
SELECT * FROM logs.events LIMIT 1;→ 必须报错ERROR 1142 (42000): SELECT command denied
如果SELECT没报错,说明权限没清干净。重点检查:INFORMATION_SCHEMA是否被意外授权(某些监控工具会悄悄加)、用户是否属于某个带SELECT的角色、或者MySQL配置启用了skip-grant-tables(开发环境偶见,生产严禁)。
INSERT不依赖SELECT,但某些场景会触发隐式读操作
单纯INSERT ... VALUES完全不需要SELECT权限。但以下情况例外,会导致报错或行为异常:
-
INSERT INTO t1 SELECT * FROM t2—— 这条语句需要对t2有SELECT权限,否则报错 - 插入前触发器(BEFORE INSERT)里执行了SELECT,而触发器定义者权限不足或SQL SECURITY为DEFINER时,可能绕过当前用户限制
- 使用
INSERT ... ON DUPLICATE KEY UPDATE时,MySQL内部要先查主键/唯一键是否存在,但这属于引擎层行为,不走用户权限校验,不影响权限设置逻辑
所以,只要业务代码不写INSERT ... SELECT,且不依赖触发器读数据,纯INSERT权限就是干净隔离的。最容易被忽略的是应用日志里埋的调试SQL——上线前务必扫一遍代码,删掉所有SELECT语句。


















