直接授予SELECT权限可行,但须分步执行CREATE USER和GRANT、限定库和主机、用反引号包裹库名、逐库授权、FLUSH PRIVILEGES,并验证INSERT报错且SHOW GRANTS仅含SELECT。

直接给开发环境用户只授 SELECT 权限是可行的,但必须避开几个关键陷阱:权限范围写错、主机名匹配失败、遗漏 FLUSH PRIVILEGES、以及误以为只授 SELECT 就万无一失(其实 SHOW CREATE VIEW 或 SELECT 系统表仍可能暴露结构)。
CREATE USER 和 GRANT 语句必须分两步执行
MySQL 8.0+ 不允许在一条 CREATE USER 语句里同时指定密码和权限。常见错误是把 GRANT 写成 CREATE USER ... IDENTIFIED BY ... WITH GRANT OPTION,这会报错 ERROR 1064 (42000)。
- 先创建用户:
CREATE USER 'dev_ro'@'192.168.10.%' IDENTIFIED BY 'StrongPass!2026'; - 再授权:
GRANT SELECT ON `app_dev`.* TO 'dev_ro'@'192.168.10.%'; - 必须刷新:
FLUSH PRIVILEGES;(尤其在低版本或权限表直改后)
数据库名和通配符不能写错
GRANT SELECT ON app_dev.* 中的 app_dev 是实际数据库名,必须和 SHOW DATABASES; 输出完全一致(含大小写,Linux 下敏感)。用反引号包裹更安全:GRANT SELECT ON `app_dev`.* TO ...;如果写成 GRANT SELECT ON *.*,就变成全局只读,开发连 information_schema 都查不了,IDE 连接时可能报错。
- 确认库存在:
SHOW DATABASES LIKE 'app_dev'; - 禁止用
app_dev.%—— MySQL 不支持这种写法,%只能用于主机名或用户名部分 - 若需多库只读,逐条授权:
GRANT SELECT ON `log_db`.* TO ...;,不要试图用逗号合并
验证权限是否真正生效
用新用户登录后执行 SELECT 能成功不等于权限设置正确——得验证“写操作真被拦住”。常见疏漏是没测试 INSERT INTO ... SELECT 或 CREATE TEMPORARY TABLE,这两者在只读用户下也可能失败或意外成功。
- 登录验证:
mysql -u dev_ro -p -h db-dev.example.com - 检查权限:
SHOW GRANTS FOR 'dev_ro'@'192.168.10.%';输出应只含SELECT行 - 主动触发拒绝:
INSERT INTO users(name) VALUES('test');应报ERROR 1290 (HY000): The MySQL server is running with the read-only option...或ERROR 1142 (42000): INSERT command denied...
开发环境还要防“只读但可删库”这类逻辑漏洞
仅靠 GRANT SELECT 无法阻止用户执行 DROP DATABASE app_dev 吗?答案是:能阻止——因为 DROP DATABASE 需要 DROP 权限,没授予就不会成功。但要注意,如果用户有 PROCESS 或 SUPER 权限(哪怕只读用户被误加),就可能绕过限制。所以务必检查:
- 确认没额外授权:
SELECT * FROM mysql.user WHERE User = 'dev_ro'\G,重点看Super_priv、Process_priv字段是否为N - 避免用
'dev_ro'@'%'——开放任意 IP 会放大风险,开发环境应限定内网段,如'dev_ro'@'192.168.10.%' - 如果用 MySQL 8.0.22+,可额外执行:
ALTER DATABASE app_dev READ ONLY = 1;,这是数据库级只读开关,比用户权限更底层,且对所有连接立即生效
最容易被忽略的一点:应用连接池或 ORM 框架可能默认尝试 SET autocommit=0 或 START TRANSACTION,而只读用户执行这些不会报错,但后续的 SELECT 若落在事务里,可能因隔离级别问题读到旧数据——这不是权限问题,却是只读配置后真实影响开发调试的细节。


















