MySQL权限必须绑定到'username'@'host'账户,授予权限时需明确指定库名加.(如myapp_db.),禁止省略;执行后须FLUSH PRIVILEGES生效,且要区分localhost与%的连接匹配逻辑。
GRANT 精确授予指定数据库权限直接给用户分配「只对某个库有操作权」,是 MySQL 权限管理中最常见也最易出错的场景。核心在于:权限不是绑定到“用户”本身,而是绑定到 'username'@'host' 这个完整账户标识上,且必须显式指定数据库范围(如 myapp_db.*),不能只写库名或留空。
GRANT SELECT, INSERT ON myapp_db.* TO 'appuser'@'192.168.1.%'; —— 正确:明确限定库、通配表、限制来源 IP 段GRANT ALL ON myapp_db TO 'appuser'@'%'; —— 错误:缺少 .*,MySQL 会报错 ERROR 1064 (42000)
GRANT SELECT ON *.* TO '
appuser'@'%'; —— 危险:这是全局只读,可能越权访问其他业务库执行后务必加 FLUSH PRIVILEGES;,否则权限不会立即生效(尤其在非 root 用户修改后或配置了 skip-grant-tables 时)。
'%' vs 'localhost':为什么授权后还是连不上?MySQL 把 'user'@'localhost' 和 'user'@'%' 当作两个完全独立的账户。如果你用客户端从本机连接却用了 mysql -h 127.0.0.1 -u appuser,实际走的是 TCP 连接,匹配的是 'appuser'@'%',而非 'appuser'@'localhost'(后者仅匹配 Unix socket 或 -h localhost 且未启用 skip-name-resolve 时)。
'appuser'@'localhost',再授 'appuser'@'127.0.0.1',覆盖两种本地连接方式'appuser'@'10.10.20.%')而非 %,避免暴露给公网3306 端口,且 MySQL 配置中 bind-address 不能为 127.0.0.1(否则拒绝远程)只给 SELECT 不等于“只读”——如果用户还能 DROP TABLE 或 CREATE VIEW,仍可能间接破坏数据结构或绕过权限逻辑。最小化原则下,应显式排除高危操作。
CREATE USER 'reporter'@'%' IDENTIFIED BY 'StrongPass!2025'; GRANT SELECT ON finance_db.* TO 'reporter'@'%'; -- 不要 GRANT SHOW VIEW、LOCK TABLES、EXECUTE,除非明确需要 REVOKE FILE, PROCESS, SUPER, REPLICATION CLIENT ON *.* FROM 'reporter'@'%'; FLUSH PRIVILEGES;
注意:REVOKE 只能撤销之前 GRANT 过的权限;若用户已有全局权限,必须先 REVOKE ALL PRIVILEGES ON *.* 再逐库授权,否则旧权限残留。
SHOW GRANTS 的真实含义SHOW GRANTS FOR 'appuser'@'%'; 显示的是该账户被显式授予的权限,**不包含隐式继承或默认权限**(比如所有账户默认都有 USAGE,即连接权)。它也不会合并多个 GRANT 语句的结果——如果分别授了 SELECT 和 INSERT,这里会显示两条独立记录。
UPDATE 权:直接用该用户登录,执行 UPDATE test_table SET x=1 WHERE 1=0; 看是否报 ERROR 1142 (42000): UPDATE command denied
SELECT * FROM mysql.db WHERE User='appuser' AND Db='myapp_db'; 可查数据库层级权限原始记录(需 SELECT 权限访问系统表)mysql.user 表后,必须 FLUSH PRIVILEGES;,否则新连接仍按旧缓存判断权限粒度越细,排查路径越长。别迷信 SHOW GRANTS 输出,真要用时,始终以「该用户实际能否执行」为准。