grant表级权限必须显式指定db_name.table_name格式,漏写数据库名会导致误授到默认库或报错;表名含特殊字符需用反引号包裹,通配符仅适用于表名(如log_%),库级权限(db.*)会覆盖细粒度表级授权。

GRANT 表级权限必须带 db_name.table_name 格式
漏写数据库名是最高频的错误。写成 GRANT SELECT ON users TO 'u'@'%' 看似合法,但 MySQL 会把它解析为对当前默认库(通常是 mysql)下的 users 表授权,而非你目标库里的同名表。更糟的是,某些版本直接报错 ERROR 1144 (42000): Illegal GRANT/REVOKE command。
正确写法只有一条规则:库名和表名必须用点号连接,且不可省略库名:
GRANT SELECT, INSERT ON myapp.orders TO 'reporter'@'192.168.1.%'GRANT UPDATE, DELETE ON billing.invoices TO 'admin'@'localhost'- 表名含特殊字符(如短横线、空格)必须用反引号:
GRANT SELECT ON `log-2024`.`errors` TO 'logger'@'%'
通配符只能用于表名,mydb.* 是库级,不是表级
想批量授权某库下所有日志表?可以用 mydb.`log_%` 这种带反引号的模式匹配,它能命中 log_errors、log_api 等表。但注意:mydb.* 是明确的库级权限,它覆盖该库下所有现有及未来新建的表,无法降级为“表级集合”。
常见误操作:
- 以为
GRANT SELECT ON mydb.`log_%` TO 'u'@'%'是“多张表的表级权限”——它确实是,但权限粒度仍是表级,每张匹配的表单独生效 - 用
mydb.*授权后又想单独收回某张表权限:不行,库级权限存在时,REVOKE SELECT ON mydb.sensitive_table无效,必须先REVOKE SELECT ON mydb.*,再逐表重授 - 系统库如
information_schema、performance_schema不支持任何表级授权,语句虽不报错,但权限实际不生效
权限叠加导致 REVOKE 失效的典型场景
MySQL 权限是“授予即生效,且粗粒度覆盖细粒度”,没有拒绝(DENY)语义。这意味着你给用户授了 SELECT ON myapp.*,再执行 REVOKE SELECT ON myapp.users,这个 REVOKE 实际被忽略——因为库级权限依然允许查 users 表。
验证和清理的关键动作:
- 查真实生效权限用:
SHOW GRANTS FOR 'u'@'%',重点看输出里是否还残留ON myapp.*这类宽泛条目 - 要真正禁用某张表,必须先
REVOKE所有更粗粒度权限(如库级),再显式授予其余表 - 权限修改后,新连接立即生效;但已存在的连接不会自动刷新权限,需断开重连
- 权限数据存于
mysql.db和mysql.tables_priv,只要mysql.db中对应库的权限位为Y,它就盖过tables_priv的细粒度设置
列级权限和表级权限不能混用,且字段访问绕过风险极高
列级 SELECT (id, name) 只在用户**完全没有表级 SELECT 权限**时才起作用。如果已授 SELECT ON myapp.users,再补一句 GRANT SELECT (id, name) ON myapp.users,后者完全无效。
即便列级权限生效,也挡不住间接读取:
-
SELECT u1.id, u2.phone FROM users u1 JOIN users u2 ON u1.id = u2.id—— 只要用户对users表有任意列的SELECT权限,JOIN就能把未授权字段拉出来 - 子查询
(SELECT phone FROM users LIMIT 1)同样绕过列级限制 -
SHOW COLUMNS FROM users这类元数据操作不受列级权限约束,所有字段名都可见
真正可控的边界是视图:CREATE VIEW safe_users AS SELECT id, name FROM users,然后只授视图权限并撤掉原表权限。否则,列级控制只是表面安全。











