生产环境mysql必须遵循最小权限原则,禁用root连接、grant option和@'%'通配符,启用ip白名单、强密码策略与账号级连接限制;开发环境应隔离dev_*库并单独授权。

开发环境用 root 权限连 MySQL 很常见,但绝不该在生产环境这么做
生产环境的 MySQL 账号必须遵循最小权限原则:只给业务实际需要的库、表、操作类型。开发环境虽然宽松些,但若共用同一套权限模型(比如用 CREATE USER + GRANT 脚本统一初始化),反而容易把开发误操作带到线上。真实事故里,不少“删库跑路”起点就是开发账号被提权或密码泄露后拥有 DROP DATABASE 权限。
实操建议:
- 生产环境禁止任何账号拥有
GRANT OPTION,避免权限二次扩散 - 开发环境可单独建
dev_*前缀数据库,对应账号只GRANT ALL PRIVILEGES ON dev_%.* TO 'dev_user'@'%',不碰正式库名 - CI/CD 流水线中执行 SQL 的账号(如 Flyway 或 Liquibase)应独立于应用连接账号,且仅赋予
SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX,去掉DROP和CREATE VIEW
MySQL 8.0 的角色(ROLE)机制对环境隔离很实用
MySQL 5.7 及以前只能靠账号粒度授权,而 8.0+ 支持 CREATE ROLE + GRANT ... TO role_name,再把角色赋给用户。这对区分环境特别友好——比如建一个 app_prod_reader 角色,只含 SELECT 权限,再把该角色赋予所有生产只读服务账号;开发环境则用 app_dev_full 角色,包含 TRUNCATE 和 EVENT 权限(方便清测试数据和调度任务)。
注意点:
- 角色默认不自动激活,需显式执行
SET ROLE app_prod_reader或在账号创建时设DEFAULT ROLE -
SHOW GRANTS FOR CURRENT_USER不显示角色继承的权限,得用SHOW GRANTS FOR CURRENT_USER USING app_prod_reader查看实际生效权限 - 角色不能跨实例复用,备份恢复后需重新
CREATE ROLE并授权
连接来源 IP 白名单在生产环境必须启用,开发环境可适度放宽
生产环境的 MySQL 用户必须绑定明确的客户端网段,比如 'app_service'@'10.20.30.%',而不是通配符 'app_service'@'%'。哪怕内网环境,也防内部横向渗透。开发环境可接受 'dev_user'@'192.168.%' 这类较宽泛范围,但不应开放到公网或 0.0.0.0/0。
常见疏漏:
- 忘记清理测试账号的
@'%'记录,导致上线后暴露攻击面 - 容器化部署时,应用 Pod IP 动态变化,误用
@'%'替代合理的 Service CIDR 段 - 云数据库(如阿里云 RDS)的白名单是实例级配置,和 MySQL 内部账号的 host 是两层控制,两者都要设
密码策略和超时设置在生产环境要更严格
开发环境可以关掉 validate_password 插件图省事,但生产环境必须开启,并设 validate_password.length = 12、validate_password.mixed_case_count = 1 等。另外,生产账号应设 PASSWORD EXPIRE INTERVAL 90 DAY,并搭配 FAILED_LOGIN_ATTEMPTS = 5 + PASSWORD_LOCK_TIME = 1 防暴力破解。
容易被忽略的细节:
-
max_connections和wait_timeout是全局变量,但账号级的MAX_CONNECTIONS_PER_HOUR才能限制单个应用异常建连风暴 - 使用
ALTER USER ... PASSWORD EXPIRE后,首次登录会强制改密,但某些 ORM(如旧版 Django)不支持交互式改密流程,需提前在应用层兼容 - MySQL 8.0 默认认证插件是
caching_sha2_password,老客户端(如 MySQL 5.7 客户端或部分 JDBC 驱动)需显式指定useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true,否则连不上
权限设计真正难的不是语法,而是每次加一条 GRANT 前,得想清楚这个权限在故障时会不会放大影响、在泄露时会不会变成突破口。很多团队卡在“不知道该收多紧”,其实就从禁掉所有 @'%'、删光 GRANT OPTION、给每个服务配独立账号这三件事开始,已经挡住 80% 的基础风险。










