生产环境只读账号必须分步创建:先create user指定ip和密码,再grant select限定库表,最后flush privileges;禁用'%'和'localhost',避免caching_sha2_password兼容问题及越权风险。

直接给结论:生产环境只读账号必须用 CREATE USER + GRANT SELECT 分步创建,主机地址限定为具体业务服务器IP,严禁用 '%',且不能跳过 FLUSH PRIVILEGES。
为什么不能用 GRANT ... IDENTIFIED BY 一步创建
MySQL 8.0 默认启用 caching_sha2_password 认证插件,而一步式 GRANT ... IDENTIFIED BY 在某些客户端(尤其是老版本 JDBC 驱动或 Shell 脚本)下会触发认证失败,报错 Authentication plugin 'caching_sha2_password' cannot be loaded。分步创建能明确控制密码哈希方式,也更利于审计追溯。
实际操作中建议:
- 先执行
CREATE USER 'app_reader'@'192.168.5.42' IDENTIFIED BY 'StrongReadPass2026!'; - 再执行
GRANT SELECT ON app_db.* TO 'app_reader'@'192.168.5.42'; - 最后执行
FLUSH PRIVILEGES;—— 这步不能省,否则权限不生效
SELECT 权限要精确到库还是表
绝大多数业务场景只需数据库级只读:GRANT SELECT ON app_db.* TO ...。但要注意:
- 如果应用只查几张核心表(比如
orders、users),应缩小范围:GRANT SELECT ON app_db.orders TO ...和GRANT SELECT ON app_db.users TO ... - 不要用
GRANT SELECT ON *.*—— 这是全局只读,仍能看到mysql、information_schema等系统库,违反最小权限原则 - 若需跨库查询(如报表服务),授权多个库:
GRANT SELECT ON report_db.* TO ...和GRANT SELECT ON app_db.* TO ...,而非合并成一个语句
主机地址写 'localhost' 还是具体 IP
生产环境一律禁用 '%' 和 'localhost'(除非该账号纯本地运维使用)。真实部署时:
- Web 应用服务器 IP 是
192.168.5.42,就写'app_reader'@'192.168.5.42' - 若有多台应用服务器,逐个授权:
'app_reader'@'192.168.5.42'、'app_reader'@'192.168.5.43',不要图省事写成'192.168.5.%' -
'localhost'只用于 DBA 本地登录维护,和应用账号物理隔离;它走 socket 连接,'127.0.0.1'则走 TCP,二者权限不互通
验证账号是否真只读、无意外权限
创建后务必用该账号登录并实测,重点检查三件事:
- 执行
SHOW GRANTS FOR 'app_reader'@'192.168.5.42';—— 输出里只能有SELECT,绝不能出现INSERT、UPDATE、DROP或USAGE之外的全局权限 - 尝试
INSERT INTO app_db.users VALUES (...);—— 应报错ERROR 1142 (42000): INSERT command denied - 执行
SELECT COUNT(*) FROM mysql.user;—— 应报错ERROR 1142 (42000): SELECT command denied,证明看不到系统表
真正容易被忽略的是:权限变更后没刷新、主机地址写宽泛、以及误授了 SHOW VIEW 或 LOCK TABLES 这类隐含高危权限 —— 它们不属于 SELECT,但能辅助数据导出或阻塞写操作。











