只授SELECT权限、不授UPDATE/DELETE是最直接的表级只读控制方式;需显式授予SELECT,确保未授予任何写权限,且REVOKE须严格匹配原授权四元组,否则静默失败。
MySQL 表级权限默认是“全无”,没显式授予就什么也干不了。想让某用户只能查不能改,核心就是只执行 GRANT SELECT ON db_name.table_name TO 'user'@'host',其他权限一个不给。
常见错误是顺手写了 GRANT ALL PRIVILEGES 或漏掉 UPDATE、DELETE 的显式拒绝——MySQL 没有“拒绝”语法,只有“授予”和“未授予”。只要没授,就天然禁止。
SHOW GRANTS FOR 'user'@'host' 确认输出里没有 UPDATE、DELETE、ALL PRIVILEGES
REVOKE UPDATE, DELETE ON db_name.table_name FROM 'user'@'host' 精准回收,不能只靠“不授新权限”INSERT 是独立权限,不授就不会插入;但 SELECT + INSERT 组合常被当成“只读”误解,其实它允许新增数据执行 REVOKE UPDATE ON sales_db.orders FROM 'reporter'@'10.20.%' 却发现还能 UPDATE,大概率是四元组(库名、表名、用户名、host)没对齐。
MySQL 的 REVOKE 是精确匹配机制,任何一处差异都会导致静默失败:大小写不一致、少反引号、host 写成 '%' 而实际是 '10.20.15.8'、库名多空格或少下划线——全都不报错,但权限纹丝不动。
SHOW GRANTS FOR 'user'@'host' 看原始授权语句,复制粘贴改 GRANT 为 REVOKE 最保险SHOW GRANTS 确认输出里那条权限真的没了KILL 对应线程或让应用重连才生效MySQL 没有 UPDATE (col_a) 这种列级写权限。想让 amount 字段不可改,但允许改 status,不能靠 GRANT 解决。
可行方案只有两个,且各有硬伤:
CREATE VIEW orders_ro AS SELECT id, status, created_at FROM orders,再授 UPDATE 权限给视图,并加 WITH CHECK OPTION。但所有写操作必须走视图,直连基表就失效BEFORE UPDATE ON orders 里判断 IF OLD.amount != NEW.amount THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'amount is read-only'; END IF;。但触发器无法阻止 UPDATE ... SET @var := amount 这类间接读取,且批量更新时性能明显下降执行完 GRANT 或 REVOKE 后,用户连上来还是报 ERROR 1142 (42000): UPDATE command denied 或反而能操作,问题往往不在语句本身。
SELECT USER(), CURRENT_USER(); —— 前者是登录身份,后者才是权限匹配依据,两者可能不同'appuser'@'localhost' 和 'appuser'@'127.0.0.1' 是两个用户,DNS 反查开启时还可能因域名解析延迟错配mysql_native_password 插件时,某些连接池会缓存认证态,重连也不刷新权限,需重启应用或清空连接池真正卡住人的从来不是语法对不对,而是你改的是 'user'@'10.20.%',而应用连的是 'user'@'10.20.30.5'——差一个点,权限就完全不生效。