MySQL 8.0 必须先 CREATE USER 再 GRANT 权限,不可一步授权;只读账号需显式 REVOKE 所有写权限并限定库作用域,且 'ro_user'@'%' 不匹配 localhost,须单独授权。

直接给用户授 GRANT SELECT 不等于只读,必须配合 REVOKE 清理残留写权限,并显式限制作用域为单库——否则哪怕只执行过一次 GRANT ALL ON *.*,后续所有库级授权都形同虚设。
先创建用户再授权,MySQL 8.0 不支持一步到位
MySQL 8.0 强制分离用户创建与权限授予,不能在 CREATE USER 里指定数据库权限。常见错误是沿用老版本写法:GRANT SELECT ON mydb.* TO 'ro_user'@'%' IDENTIFIED BY 'pwd',这在 8.0 会报错。
- 正确顺序:先
CREATE USER 'ro_user'@'10.20.%' IDENTIFIED WITH mysql_native_password BY 'StrongPass2026!';(指定插件防连接失败) - 再
GRANT SELECT ON `mydb`.* TO 'ro_user'@'10.20.%';(注意反引号包裹库名,避免关键字冲突) - 主机名必须精确匹配连接来源,
'ro_user'@'localhost'和'ro_user'@'%'是两个独立账号
只读 ≠ 只 GRANT SELECT,必须 REVOKE 写类权限
MySQL 权限是叠加的,不是覆盖的。GRANT SELECT 不会自动取消之前授予的 INSERT 或 ALL PRIVILEGES。如果用户曾被授过全局权限,只加 SELECT 完全无效。
- 执行
SHOW GRANTS FOR 'ro_user'@'10.20.%';查当前所有权限,确认无INSERT、UPDATE、DELETE、DROP、ALTER、EXECUTE、LOCK TABLES等 - 如有,逐项
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, EXECUTE, LOCK TABLES ON `mydb`.* FROM 'ro_user'@'10.20.%'; - 特别检查
SUPER:运行SELECT Super_priv FROM mysql.user WHERE User='ro_user' AND Host='10.20.%';,若为Y,必须REVOKE SUPER ON *.* FROM 'ro_user'@'10.20.%';
验证时别信 SHOW GRANTS,要真连、真操作
权限缓存在连接会话中,旧连接不会自动刷新。验证必须开新连接,且测试边界行为——很多“只读”账号在 SELECT ... FOR UPDATE 或跨库查询时意外失败,其实是权限设计遗漏。
- 用新终端执行:
mysql -u ro_user -p -h db-host -D mydb - 成功:
SELECT COUNT(*) FROM users; - 必须失败:
INSERT INTO users(name) VALUES('test');(报ERROR 1142) - 必须失败:
USE other_db;(报ERROR 1044),以及SELECT * FROM other_db.logs; - 若需
SHOW CREATE TABLE,额外授GRANT SELECT ON `information_schema`.`TABLES` TO 'ro_user'@'10.20.%';
容易被忽略的隐式写依赖和 host 匹配细节
真正安全的只读账号,不止防 INSERT,还要堵住所有可能触发写行为的路径。而最常踩的坑,是以为 'ro_user'@'%' 能匹配所有 IP,其实它不匹配 localhost ——本地连接走的是 socket,host 匹配 'ro_user'@'localhost' 才生效。
-
CREATE TEMPORARY TABLE需要CREATE TEMPORARY TABLES权限,且该权限只能全局授予(ON *.*),一旦开了,就突破了库级只读边界 - 存储过程内若访问其他库表,权限检查发生在执行时,调用者必须对目标库也有
SELECT,否则报错;但定义者权限不影响调用者限制 - 若应用从容器或跳板机连接,
host应填实际出口 IP 段(如'10.20.30.%'),而非'%',否则等同开放外网 - MySQL 8.0.22+ 支持
ALTER DATABASE mydb READ ONLY = 1;,这是库级开关,但它是实例级策略,影响所有用户,不是账号级方案


















