mysql只读用户需显式授予select权限且不授任何写权限,必须检查并清理已有权限、精确限定主机范围、禁用grant option、限制系统库访问,并验证新连接生效。

只授 SELECT 权限,其他权限一个不给
MySQL 默认不授予任何权限,所以“只读”不是靠禁止写操作实现的,而是靠**不授予写权限**。只要没执行 GRANT INSERT、GRANT UPDATE、GRANT DELETE 或 GRANT ALL PRIVILEGES,用户就天然不能执行这些命令。
常见错误是顺手写了 GRANT ALL ON mydb.* 后又想靠 REVOKE 补救——但权限叠加逻辑复杂,容易残留;最稳妥的方式是从头只授 SELECT:
- 新建用户后立即执行:
CREATE USER 'ro_user'@'192.168.1.%' IDENTIFIED WITH mysql_native_password BY 'strong_pwd'; - 仅授权查询:
GRANT SELECT ON `mydb`.* TO 'ro_user'@'192.168.1.%'; - 确认无多余权限:
SHOW GRANTS FOR 'ro_user'@'192.168.1.%';输出里不应出现INSERT、UPDATE、DELETE、GRANT OPTION或ALL PRIVILEGES
为什么用户还能删表或改结构?检查 GRANT OPTION 和 host 匹配
即使只授了 SELECT,用户仍可能执行 DROP TABLE 或 ALTER TABLE,根本原因通常是:该账号曾被授予 GRANT OPTION,或存在匹配更宽泛的旧账号(比如 'ro_user'@'%' 和 'ro_user'@'localhost' 是两个独立账号)。
必须显式回收高危权限:
REVOKE GRANT OPTION ON `mydb`.* FROM 'ro_user'@'192.168.1.%';- 如果曾执行过
GRANT ... ON *.*,要精准撤销:REVOKE ALL PRIVILEGES, GRANT OPTION ON *.* FROM 'ro_user'@'192.168.1.%'; - 检查 host 是否完全一致:
'ro_user'@'192.168.1.%'无法覆盖'ro_user'@'10.0.2.5',后者若存在且有更高权限,就会生效
列级 SELECT 权限不等于字段隔离,别指望它防 JOIN 泄露
想限制用户只能查 id 和 name,而屏蔽 phone?直接授 GRANT SELECT(id, name) ON mydb.users 看似可行,但实际防护力极弱:
- 前提是必须先
REVOKE SELECT ON mydb.users FROM 'ro_user'@'...';,否则全表权限会覆盖列级声明 - 列级权限只拦直查:
SELECT * FROM users会报错,但SELECT u1.id, u2.phone FROM users u1 JOIN users u2 ON u1.id = u2.id依然能拿到phone - 子查询、UNION、派生表等同样绕过列白名单;MySQL 不做结果集投影过滤,只做语法层字段检查
- 真正可控的方案是建视图:
CREATE VIEW safe_users AS SELECT id, name FROM users;,再GRANT SELECT ON mydb.safe_users,并REVOKE SELECT ON mydb.users
元数据和系统表访问不受 SELECT 权限约束
用户只要对任意一张表有 SELECT 权限,就能执行 SHOW TABLES、SHOW COLUMNS FROM users,甚至查 INFORMATION_SCHEMA.TABLES 推断出其他库表名——这不是漏洞,是 MySQL 设计如此。
如需收紧元数据可见性:
- MySQL 8.0.12+ 支持:
REVOKE SELECT ON `INFORMATION_SCHEMA`.* FROM 'ro_user'@'192.168.1.%'; - 禁用
performance_schema访问(若不需要):REVOKE SELECT ON `performance_schema`.* FROM 'ro_user'@'192.168.1.%'; - 注意:
SHOW CREATE TABLE需要SELECT+ 对应表的SHOW VIEW权限;没授SHOW VIEW时,该语句会失败,但DESCRIBE table_name仍可成功
最易被忽略的点:活跃连接不会自动刷新权限。用户已建立的连接仍持有旧权限快照,必须重连才能生效;另外,视图定义者权限(DEFINER)和 SQL SECURITY 设置会影响视图内联查询是否受调用者权限限制,不能默认认为视图一定安全。











