MySQL列级权限需叠加表级权限才生效,仅支持SELECT/INSERT/UPDATE/REFERENCES操作,且WHERE等子句涉及的列也须授权;其不适用于核心数据隔离,推荐用视图或库级隔离替代。

MySQL列级权限能实现字段粒度的访问控制,但不是“开箱即用”的安全护栏——它必须和表级权限叠加生效,且仅覆盖SELECT、INSERT、UPDATE、REFERENCES四类操作;DELETE和ALTER天然不支持列级授权。
列级权限必须依赖表级权限才有效
单独执行GRANT SELECT (name) ON mydb.users TO 'report'@'localhost'不会起作用,除非该用户已拥有SELECT表级权限。MySQL权限系统是“叠加”而非“替代”:列级只是在已有表权限基础上进一步缩小可操作字段范围。
- 检查是否已有表级权限:
SHOW GRANTS FOR 'report'@'localhost',输出中应含类似GRANT SELECT ON `mydb`.`users` TO 'report'@'localhost' - 若无,则先补表级授权:
GRANT SELECT ON mydb.users TO 'report'@'localhost' - 再加列级限制:
GRANT SELECT (id, name, email) ON mydb.users TO 'report'@'localhost' - 注意:如果后续又执行了
GRANT SELECT ON mydb.users TO ...(全表),列级限制即被覆盖失效
WHERE/ORDER BY/GROUP BY涉及的列也必须授权
列级SELECT权限不是只管SELECT子句里的字段。只要查询中任何位置(包括WHERE条件、ORDER BY、GROUP BY、HAVING)引用了未授权列,就会报错ERROR 1142 (42000): SELECT command denied to user for column 'xxx'。
- 例如用户只有
SELECT (name)权限,却执行SELECT name FROM users WHERE id = 1→ 报错,因为id未授权 - 解决方法是把
WHERE里用到的列一并加入授权列表:GRANT SELECT (id, name) ON mydb.users TO 'report'@'localhost' - 视图可以缓解这个问题(定义时固化过滤逻辑),但调用视图仍受调用者列权限约束
列权限查不到SHOW GRANTS里,得查mysql.columns_priv
SHOW GRANTS只显示显式执行过的GRANT语句,不反映列级权限的实际状态。真正生效的列权限记录在mysql.columns_priv表中,且Column_priv字段是逗号分隔字符串(如Select,Update),不是布尔值。
- 查某用户的列权限:
SELECT * FROM mysql.columns_priv WHERE User='report' AND Host='localhost' AND Db='mydb' AND Table_name='users' - 手动插入或更新
mysql.columns_priv后,必须执行FLUSH PRIVILEGES才会生效 - 注意:
GRANT语句自动刷新权限缓存,无需FLUSH PRIVILEGES;只有直改系统表才需要
列级权限不适合做核心数据隔离手段
它容易被绕过,也不适合审计和迁移。客户端工具发SELECT *时,MySQL会返回所有有SELECT权限的列,其余列为NULL——但字段名和表结构仍暴露;ORM可能忽略列限制,直接构造全字段查询;备份恢复时mysql.columns_priv容易遗漏。
- 更可靠的做法是用视图封装字段+行过滤逻辑,然后只授予视图权限,同时禁用原表访问
- 租户隔离必须走库级硬隔离(
GRANT ... ON `tenant_a`.*),列级权限在这里毫无意义 - 真正难处理的从来不是“能不能授权”,而是“权限变更后应用是否还兼容”——比如新增一个
phone字段用于UPDATE,就得同步补列级UPDATE (phone),否则业务直接报错


















