MySQL 的权限模型基于「用户+主机+密码」三元组
MySQL 不像 PostgreSQL 那样按数据库用户全局唯一,而是把
'user'@'host'当作一个独立账号。比如
'app'@'192.168.1.%'和
'app'@'localhost'是两个完全无关的账户,权限需分别授予。常见错误是只给远程 IP 授权却忘了
'localhost',导致本地命令行连得上、程序连不上——因为 MySQL 客户端默认用
localhost连接时走 socket,实际匹配的是
'user'@'localhost',而非
'user'@'%'。
创建用户必须显式指定 host:
CREATE USER 'dev'@'10.0.2.%' IDENTIFIED BY 'pwd123';
不写 host 默认是
'%',但这是模糊且不安全的,应避免。
GRANT 语句不自动刷新权限,但 FLUSH PRIVILEGES 多数时候没必要
执行
GRANT或
CREATE USER后,权限已写入
mysql.user等系统表,并由服务端即时加载——不需要手动
FLUSH PRIVILEGES。只有在直接 UPDATE 系统表(如绕过 GRANT 修改
mysql.db)后才需要刷新。 ✅ 正确做法:
GRANT SELECT ON sales.* TO 'report'@'%';❌ 多余操作:
GRANT ...; FLUSH PRIVILEGES;⚠️ 真要用到 FLUSH:仅当你用
UPDATE mysql.user SET authentication_string=...这类直改系统表的操作之后
权限层级混乱是线上事故高发区
MySQL 权限分五层:全局(
*)、库级(
db.*)、表级(
db.tbl)、列级(
db.tbl(col1))、存储过程级。低层级权限不会继承高层级,且**同名账号在不同层级的权限是叠加的**。
典型陷阱:
给'api'@'%'授予
SELECT在
shop.users表,又单独给
'api'@'%'授予
INSERT在
shop.orders表——这没问题; 但如果误对
'api'@'%'执行了
REVOKE ALL ON *.* FROM 'api'@'%',会清掉所有全局和库级权限,但**表级、列级权限不受影响**,导致行为不可预测; 更隐蔽的是:同一账号在
mysql.db和
mysql.tables_priv中都有记录时,MySQL 按“最具体匹配”生效,调试需查
SHOW GRANTS FOR 'user'@'host';而非只看某张表
生产环境禁用 IDENTIFIED WITH caching_sha2_password 的匿名访问风险
MySQL 8.0 默认认证插件是
caching_sha2_password,它要求 TLS 或安全连接,否则客户端可能静默降级失败。而很多旧脚本或中间件(如某些版本的 Django ORM)未显式配置
ssl_disabled = False,连上去就报
Access denied for user,实际是握手阶段被拒绝,不是权限问题。
解决方式(选其一):
创建用户时强制指定旧插件:CREATE USER 'svc'@'%' IDENTIFIED WITH mysql_native_password BY 'xxx';或在服务端配置
default_authentication_plugin=mysql_native_password(需重启) 或确保客户端连接串含
?ssl-mode=DISABLED(仅测试环境,勿用于生产)
权限本身没毛病,但认证链断了,
SHOW GRANTS看起来一切正常,排查时容易卡在权限逻辑里绕圈子。
