必须为每个应用或角色单独创建用户并按需授权,严禁复用root;create user必须显式指定host,如'api_user'@'192.168.5.10',避免默认@'%'带来的安全风险;grant应严格收敛至数据库或表级,禁用.和all privileges;mysql 8.0+推荐使用角色统一管理权限;密码须启用强策略并设置过期,权限变更后无需flush privileges,除非直接修改mysql.user表。

直接结论:不要复用 root,必须为每个应用或角色单独创建用户,并按需授予权限——这是避免权限爆炸、误操作和横向渗透的底线。
CREATE USER 语句必须显式指定 host
MySQL 用户名本质是 'username'@'host' 这个组合,漏掉 @'host' 或写成 @'' 会导致行为不可控。比如:
-
CREATE USER 'api_user'等价于'api_user'@'%'(允许任意主机连接),生产环境严禁这样写 -
CREATE USER 'api_user'@'localhost'只允许本机 Unix socket 或 127.0.0.1 连接 -
CREATE USER 'api_user'@'192.168.5.10'限定单一应用服务器 IP,最安全 - 若应用部署在容器或 Kubernetes 中,
host应填宿主机 IP 或 Service DNS 名(如'api_user'@'mysql-svc.default.svc.cluster.local')
GRANT 时避免使用 *.* 和 ALL PRIVILEGES
给业务用户授 GRANT ALL PRIVILEGES ON *.* 就等于把锁匙交给清洁工还告诉他“整栋楼随便进”。真实场景应严格收敛:
- 只读服务:用
GRANT SELECT ON myapp_db.* TO 'report_user'@'192.168.5.%' - API 写入:仅需
GRANT INSERT, UPDATE, SELECT ON myapp_db.orders TO 'api_user'@'192.168.5.10' - 批量导入账号:加
LOCK TABLES权限(但不给DROP),并限制连接数:CREATE USER 'batch_user'@'192.168.5.20' IDENTIFIED BY 'pwd' WITH MAX_USER_CONNECTIONS 3 - MySQL 8.0+ 支持角色(
CREATE ROLE),可先建角色再GRANT,最后GRANT role_name TO user,便于统一管理
密码策略与过期控制不能跳过
MySQL 5.7+ 默认启用密码验证插件,但很多运维仍忽略它。必须主动配置,否则弱密码能轻易绕过:
- 检查是否启用:
SELECT plugin FROM mysql.user WHERE User = 'root';—— 应为mysql_native_password或caching_sha2_password - 强制 90 天更换:
ALTER USER 'app_user'@'%' PASSWORD EXPIRE INTERVAL 90 DAY; - 禁止重用最近 5 次密码:
ALTER USER 'app_user'@'%' PASSWORD HISTORY 5; - 如果应用连接池不支持自动重连认证(如老版本 HikariCP),密码过期会直接导致连接失败,这点常被忽略
权限生效后务必 FLUSH PRIVILEGES?不,通常不需要
用 CREATE USER 和 GRANT 创建或修改权限后,MySQL 会自动加载内存中的权限表——FLUSH PRIVILEGES 只在直接 UPDATE mysql.user 表时才需要。滥用它反而可能掩盖权限未生效的真实原因:
- 常见误判:执行
GRANT后立刻用新用户连接失败,就去FLUSH,其实真正问题是 host 不匹配或防火墙拦截 - 验证方式更可靠:
SHOW GRANTS FOR 'app_user'@'192.168.5.10';+SELECT USER(), CURRENT_USER();(后者显示实际匹配的'user'@'host') - 如果改了
mysql.user表(比如手动更新authentication_string),才必须FLUSH PRIVILEGES
真实环境中,最难的不是语法写对,而是搞清「这个用户到底从哪台机器、用什么协议、走哪个端口连进来」——host 字段必须和实际网络拓扑一致,否则所有授权都是纸面功夫。











