只读账号创建失败主因是host匹配错误、skip-grant-tables启用或未刷新权限;应显式指定host、检查配置并执行flush privileges;推荐mysql 8.0+使用角色机制统一管理只读权限。

只读账号创建失败:GRANT 之后仍无法查询?
常见现象是执行了 GRANT SELECT ON *.* TO 'ro_user'@'%' IDENTIFIED BY 'pwd';,但新用户连上就报 Access denied for user 或查不到任何表。根本原因不是权限没给,而是 MySQL 默认关闭了匿名用户、跳过密码验证,或 host 匹配不严格。
- 务必显式指定 host,
'ro_user'@'localhost'和'ro_user'@'%'是两个完全不同的账号,别混用 - 如果 MySQL 启用了
skip-grant-tables,权限系统不生效,先检查mysqld启动参数和配置文件中的skip-grant-tables是否残留 - 执行完
GRANT后必须跟FLUSH PRIVILEGES;(8.0+ 部分场景可省,但低版本和复杂部署下建议保留)
SELECT 权限粒度控制:库级 vs 表级 vs 列级
只读不等于“全库可读”——生产环境应最小化授权范围。MySQL 的 SELECT 支持三级粒度,行为和风险差异明显。
- 库级:
GRANT SELECT ON mydb.* TO 'ro_user'@'%';—— 最常用,但若库内含敏感日志表(如audit_log),就暴露了 - 表级:
GRANT SELECT ON mydb.users TO 'ro_user'@'%';—— 更安全,但需逐表授权,维护成本高;注意视图(VIEW)默认不可 SELECT,需额外授SHOW VIEW - 列级:
GRANT SELECT (id, name) ON mydb.users TO 'ro_user'@'%';—— 精确到字段,但仅支持 SELECT,且不能和表级 SELECT 混用(会冲突)
MySQL 8.0+ 的角色机制让只读更可控
直接 GRANT 容易漏、难回收。8.0 引入角色(ROLE)后,推荐用角色封装权限,再赋给用户——改权限只需动角色,不动用户。
- 创建只读角色:
CREATE ROLE 'readonly_role'; - 授库级 SELECT:
GRANT SELECT ON mydb.* TO 'readonly_role'; - 绑定用户:
GRANT 'readonly_role' TO 'ro_user'@'%'; - 启用角色(用户登录后默认不激活):
SET DEFAULT ROLE 'readonly_role' TO 'ro_user'@'%';
注意:角色在 8.0.16+ 才支持 DEFAULT ROLE 自动激活,低于该版本需用户手动执行 SET ROLE,否则权限不生效。
只读账号被误写:如何防 INSERT/UPDATE/DELETE 却仍能连上?
只读账号最大的隐患不是“看不了”,而是“意外能写”。MySQL 不提供“全局禁止写”的开关,必须靠权限隔离 + 连接层约束双保险。
- 确保没授予
INSERT、UPDATE、DELETE、DROP、ALTER等任何写权限——哪怕只漏一个UPDATE,就能通过UPDATE ... SET x=(SELECT ...)间接写入 - 避免授予
USAGE之外的全局权限(如GRANT OPTION、PROCESS),它们可能绕过限制 - 连接时加
--read-only参数无效(那是客户端提示,服务端不校验);真正有效的是在 MySQL 配置中设read_only=ON(主库慎开),或使用代理层(如 ProxySQL)拦截写语句
最易被忽略的一点:只读账号若拥有 SELECT 权限,又恰好能访问 mysql 系统库(比如授了 SELECT ON mysql.user),就可能读到其他账号密码哈希——所以系统库一律不授,除非明确需要。











