grant语句必须显式指定数据库名和点号,如grant select on app_db.users to 'u'@'%';省略库名会误授到默认库(如mysql),且库级权限会覆盖表级权限,需先revoke再逐表grant,最后flush privileges并用新连接验证。

GRANT 语句必须带完整库表路径,不能省略数据库名
MySQL 不会把 GRANT SELECT ON users TO 'u'@'%' 理解为你想授权当前业务库下的 users 表——它默认作用于当前默认库(通常是 mysql),极大概率授错对象,甚至直接报错。
真正生效的写法必须显式包含数据库名和点号:
-
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.users TO 'u'@'%'✅ -
GRANT ALL PRIVILEGES ON app_db.orders TO 'u'@'192.168.1.%'✅(支持 IP 段) -
GRANT SELECT ON users TO 'u'@'%'❌(危险:可能授到mysql.users) -
GRANT SELECT ON app_db.* TO 'u'@'%'❌(这是库级权限,不是单表)
先清理粗粒度权限,否则表级授权被覆盖
MySQL 权限是叠加生效的,且“粗粒度覆盖细粒度”。如果你之前执行过 GRANT SELECT ON app_db.* TO 'u'@'%',再单独 REVOKE SELECT ON app_db.users 并不会禁掉这张表——因为库级权限还在,它直接盖过了表级限制。
要真正实现“只给某几张表”,必须按顺序操作:
- 先撤掉所有宽泛权限:
REVOKE SELECT, INSERT, UPDATE, DELETE ON app_db.* FROM 'u'@'%' - 再逐个授予需要的表:
GRANT SELECT, INSERT ON app_db.users TO 'u'@'%'、GRANT SELECT ON app_db.logs TO 'u'@'%' - 确认无残留:
SHOW GRANTS FOR 'u'@'%'输出里不应出现ON app_db.*或ON *.*
权限变更后必须 FLUSH PRIVILEGES,且验证要用新连接
GRANT 多数情况会自动触发刷新,但不保证 100% 实时——尤其在非 root 用户操作或权限表被直改后,FLUSH PRIVILEGES 是唯一可靠手段。
更关键的是:已存在的连接会缓存旧权限。即使你刚刷完,老连接仍按旧规则校验。
- 执行
FLUSH PRIVILEGES后,务必用新客户端测试:mysql -u u -p -h host - 不要在原连接里
USE app_db再试,它可能还拿着旧上下文 - 测试语句优先用全限定名:
SELECT * FROM app_db.users,避免因默认库影响判断
查权限别只信 SHOW GRANTS,直接查 mysql.tables_priv 更准
SHOW GRANTS FOR 'u'@'%' 可能不显示你刚授的权限——MySQL 只在用户拥有该库下「任意一张表的显式权限」时,才把整个库作为上下文展示。否则它只会逐条列出你真正 GRANT 过的单表权限。
要确认权限是否真正落库,直接查底层表:
SELECT * FROM mysql.tables_priv WHERE User='u' AND Host='%' AND Db='app_db' AND Table_name='users';
如果这一行存在,且 Table_priv 字段包含 Select,Insert,Update,Delete,说明权限已写入磁盘。
视图、依赖表、SQL SECURITY 都会影响最终可查性;哪怕 GRANT SELECT ON app_db.v_summary 成功,查询时报 SELECT command denied for table 'v_summary',大概率是因为视图底层引用了用户没权限的表。











