MySQL 8.0.16+才真正支持列级SELECT权限;需先撤销表级SELECT权限,再显式授予具体列名,且WHERE/ORDER BY等子句中涉及的列也必须授权。

MySQL 8.0.16+ 才真正支持列级 SELECT 权限,低于该版本的写法能执行但不生效;必须显式指定列名、不能通配、且需先撤掉表级 SELECT 权限,否则列级限制会被绕过。
确认 MySQL 版本和基础前提
列级权限不是“加个括号就生效”的语法糖。它只在 SELECT VERSION() 返回 ≥ 8.0.16 时才起作用。5.7 或早期 8.0 版本中,GRANT SELECT (name) ON db.t TO 'u'@'%' 能成功执行,但用户登录后查 name 仍报 ERROR 1142 (42000): SELECT command denied to user。
- 必须用完整格式:
GRANT SELECT (col1, col2) ON database.table TO 'user'@'host',漏库名、错括号位置、加引号(如'col1')都会导致失败或降级为表级授权 - 用户必须已存在:
CREATE USER 'u'@'%' IDENTIFIED BY 'pwd'需先执行,MySQL 8.0+ 不再自动创建用户 - 不要依赖
SHOW GRANTS判断是否生效——它只显示授权语句,不反映实际校验逻辑
正确授予列级 SELECT 权限的步骤
列级 SELECT 权限本质是“最小交集”控制:只有语句中出现的每一列都已被单独授权,查询才通过。这意味着你不能靠“补授权”来放宽限制,而要从收紧开始。
- 先收回表级权限:
REVOKE SELECT ON mydb.users FROM 'app_reader'@'10.0.1.%'(关键!没这步,列级权限无效) - 再授具体列:
GRANT SELECT (id, name, email) ON mydb.users TO 'app_reader'@'10.0.1.%' - 权限立即生效,无需
FLUSH PRIVILEGES(仅手动改系统表才需要) - 验证方式:切换用户后执行
SELECT id, name FROM users✅;SELECT * FROM users❌;SELECT name, created_at FROM users❌(哪怕只多一列未授权)
WHERE / ORDER BY 中的列也受权限约束
很多人以为列权限只管 SELECT 子句里的字段,其实不然。MySQL 在解析阶段就检查所有涉及列的访问合法性,包括 WHERE、ORDER BY、GROUP BY 中引用的列。
- 用户只有
SELECT (name)权限,执行SELECT name FROM users WHERE id = 123会直接报错:ERROR 1142 (42000): SELECT command denied to user for column 'id' - 解决办法不是“放开所有列”,而是确保
WHERE中用到的列也在授权列表里,例如补授:GRANT SELECT (id, name) ON mydb.users TO 'app_reader'@'10.0.1.%' - 注意:
JOIN中关联条件列、子查询内层列,同样触发权限检查
权限存储位置与调试要点
SHOW GRANTS 输出不可信,列级权限实际存于 mysql.columns_priv 表,且只记录显式授予项。表级权限在 mysql.tables_priv,两者独立管理。
- 查真实列权限:
SELECT Column_name, Column_priv FROM mysql.columns_priv WHERE User='app_reader' AND Host='10.0.1.%' AND Db='mydb' AND Table_name='users' - 如果返回空,说明没生效——常见原因是版本太低、用户不存在、或
GRANT语句语法错(比如把列名写在ON后面而非权限名后) - 列名大小写敏感,取决于建表时定义(如
CREATE TABLE t (Name VARCHAR(10)),授权时必须用Name,不能写name) - 视图、存储过程中调用
SELECT,仍按调用者权限校验,不会因定义者有全权而豁免
最易被忽略的是:列权限只在没有对应表级权限时才起作用。一旦用户拥有表级 SELECT,所有列都可查,列级 GRANT 或 REVOKE 都不改变行为。真要细粒度控制,必须先 REVOKE 表级权限,再逐列 GRANT —— 这个顺序颠倒,整个方案就失效。


















