CREATE USER 和 GRANT 必须分两步执行,且需显式授予 USAGE 权限才能登录;只读账号应仅授 SELECT 权限并限定数据库范围,必要时额外授权 INFORMATION_SCHEMA 才能执行 SHOW CREATE TABLE。

初始化时直接执行 CREATE USER + GRANT 脚本
MySQL 启动时不会自动运行 SQL,但你可以在容器启动、初始化脚本或 mysqld_safe 启动参数中注入初始化逻辑。关键是把 CREATE USER 和 GRANT SELECT 写进一个 .sql 文件,再通过 --init-file 或 Docker 的 /docker-entrypoint-initdb.d/ 触发。
-
--init-file=/path/to/init.sql是最稳妥的方式:mysqld 启动时只执行一次,且在系统表初始化完成后运行,能保证mysql.user已就绪 - Docker 环境下(如官方 mysql:8.0 镜像),把 SQL 文件放到
/docker-entrypoint-initdb.d/目录即可,文件名需以.sql结尾,会按字母序执行 - 别在
my.cnf里写 SQL —— 它只认配置项,不解析语句;也别用mysql -e "..."包裹在 shell 启动脚本里,容易因连接未就绪而失败
脚本里必须包含 USAGE 权限和显式数据库范围
只写 GRANT SELECT ON myapp.* TO 'ro_user'@'%' 不够——新账号连登录都失败,因为没 USAGE 权限。MySQL 的 USAGE 是“连接权”,它不随其他权限自动赋予,必须显式声明或由 CREATE USER 自带(但仅限于空权限用户)。
-
CREATE USER本身不授任何权限,包括USAGE;所以要么补一句GRANT USAGE ON *.* TO 'ro_user'@'%',要么确保GRANT SELECT之后账号能连上 - 如果只读账号要查表结构(比如 ORM 启动时执行
SHOW CREATE TABLE),必须额外加GRANT SELECT ON INFORMATION_SCHEMA.* TO 'ro_user'@'%' - 千万别用
GRANT SELECT ON *.*初始化生产环境——它给的是全局只读,后续新建库默认不可读;应严格限定到业务库名,比如myapp_db.*
MySQL 8.0+ 密码策略会中断初始化流程
如果你的初始化脚本里写了弱密码(比如 '123456' 或纯数字),MySQL 8.0 默认开启的 validate_password 插件会让 CREATE USER 直接报错:ERROR 1819 (HY000): Your password does not satisfy the current policy requirements,整个初始化卡住。
- 临时关闭策略(仅限开发/测试环境):在 init.sql 开头加
SET GLOBAL validate_password.policy = LOW;,但注意这行必须在CREATE USER前执行 - 生产环境推荐预生成合规密码:用
openssl rand -base64 12生成,或在脚本外生成后注入变量,避免硬编码 - MySQL 8.0.28+ 支持
ALTER USER ... PASSWORD EXPIRE NEVER,初始化时顺手加上,防止首次登录被强制改密阻断服务
验证是否生效不能只靠 SELECT 成功
初始化完成后,别只测试 SELECT * FROM t 就认为 OK —— 很多框架或监控工具会悄悄执行 SELECT ... FOR UPDATE 或 SHOW FULL PROCESSLIST,这些操作即使只有 SELECT 权限也会失败。
- 真正验证点:用该账号连接后执行
SELECT 1、SHOW TABLES、SHOW CREATE TABLE t(需INFORMATION_SCHEMA权限)、SELECT COUNT(*) FROM t - 故意触发写操作:
INSERT INTO t VALUES(1)应返回ERROR 1142 (42000): INSERT command denied,而不是权限不存在类错误 - 检查权限是否绑定到正确 host:用
SHOW GRANTS FOR 'ro_user'@'%'确认输出里没有INSERTUPDATEDELETE等关键字,且ON后面的范围跟你初始化脚本一致
实际最容易被忽略的是 USAGE 权限缺失导致连接拒绝,以及 INFORMATION_SCHEMA 权限遗漏引发的元数据查询失败——这两处问题不会在初始化时报错,但上线后第一分钟就会暴露。


















