只读账号不能只靠grant select,因为mysql权限是叠加而非覆盖的,需先show grants查清旧权限,再显式revoke insert/update/drop/lock tables/super等所有写类及高危权限,并逐库授权select、验证新连接下insert报错而select成功。

只读账号为什么不能只靠 GRANT SELECT?
因为 MySQL 权限是叠加的,不是覆盖的。哪怕你刚执行了 GRANT SELECT ON `app_db`.* TO 'ro_user'@'192.168.1.%',只要该用户之前被授过 ALL PRIVILEGES 或 INSERT,那些写权限就还在——GRANT 不会自动清除旧权限。
- 必须先查清现状:
SHOW GRANTS FOR 'ro_user'@'192.168.1.%',看输出里有没有INSERT、UPDATE、DROP、LOCK TABLES、EXECUTE甚至SUPER -
USAGE权限不代表“只读”,它实际等于“无任何权限”,别把它当安全状态 - 如果输出含
GRANT OPTION,说明该用户能自行授权他人,必须显式禁用(MySQL 默认不带,但确认更稳妥)
如何真正清理出干净的只读权限?
关键不是“加什么”,而是“删干净”。只读 ≠ 只给 SELECT,而是 SELECT + 显式撤销所有可能越权的权限。
- 先授基础读权限:
GRANT SELECT ON `app_db`.* TO 'ro_user'@'192.168.1.%' - 再逐项回收写类权限:
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, TRIGGER, CREATE VIEW, SHOW VIEW, EXECUTE, LOCK TABLES, CREATE TEMPORARY TABLES ON `app_db`.* FROM 'ro_user'@'192.168.1.%' - 检查并撤销全局高危权限:
SELECT Super_priv FROM mysql.user WHERE User = 'ro_user' AND Host = '192.168.1.%',若结果为Y,必须执行REVOKE SUPER ON *.* FROM 'ro_user'@'192.168.1.%' - 特别注意:
FLUSH PRIVILEGES在直接修改系统表后才必需;但这里建议执行一次,确保缓存刷新(尤其旧版本或跳过权限表启动时)
容易被忽略的系统库和跨库风险
即使业务库权限收得再紧,用户仍可能通过 information_schema 或跨库 JOIN 泄露信息或误操作。
- 默认情况下,所有用户都能查
information_schema——这不是漏洞,是 MySQL 设计。如需限制,必须显式执行:REVOKE SELECT ON information_schema.* FROM 'ro_user'@'192.168.1.%'(需要 SUPER 权限才能 revoke 系统库) - 如果应用 SQL 含
SELECT * FROM app_db.t1 JOIN log_db.events,只授app_db的 SELECT 不够,必须额外加:GRANT SELECT ON `log_db`.* TO 'ro_user'@'192.168.1.%' - 不要用
GRANT SELECT ON *.*,哪怕只是测试——它会把mysql、performance_schema全部放开,暴露账号哈希、插件配置等敏感元数据
验证时必须用新连接,且测边界行为
权限变更对已存在的连接不生效,旧连接仍持有旧权限快照。验证必须用全新登录会话,并主动触发非 SELECT 操作。
- 用新账号登录:
mysql -uro_user -p -h db-host -P3306 - 成功执行:
SELECT COUNT(*) FROM app_db.users - 必须失败:
INSERT INTO app_db.users (name) VALUES ('x')→ 应报ERROR 1142 (42000): INSERT command denied - 必须失败:
USE mysql; SELECT User FROM user LIMIT 1→ 应报权限拒绝,而非空结果(空结果说明已意外获得 mysql 库权限) - 如果应用依赖
SHOW CREATE TABLE查结构,需额外加:GRANT SHOW CREATE TABLE ON `app_db`.* TO 'ro_user'@'192.168.1.%',但慎用PROCESS或SHOW VIEW
真正的最小权限不是配完就结束,而是每次新增表、视图、存储过程时,都要重新审视权限是否仍精确匹配——尤其是视图定义者(DEFINER)权限和 SQL SECURITY 设置,它们可能绕过你的 SELECT 限制。











