diff --git a/TOC.md b/TOC.md index a76d8dd8c0a2..8a2cfe3e6da6 100644 --- a/TOC.md +++ b/TOC.md @@ -633,6 +633,7 @@ - 权限 - [与 MySQL 安全特性差异](/security-compatibility-with-mysql.md) - [权限管理](/privilege-management.md) + - [列级权限管理](/column-privilege-management.md) - [TiDB 用户账户管理](/user-account-management.md) - [TiDB 密码管理](/password-management.md) - [基于角色的访问控制](/role-based-access-control.md) diff --git a/column-privilege-management.md b/column-privilege-management.md new file mode 100644 index 000000000000..ae38fa3c3eaa --- /dev/null +++ b/column-privilege-management.md @@ -0,0 +1,172 @@ +--- +title: 列级权限管理 +summary: TiDB 支持兼容 MySQL 的列级权限管理机制,可通过 `GRANT` 或 `REVOKE` 在表级别对指定列授予或回收 `SELECT`、`INSERT`、`UPDATE`、`REFERENCES` 权限,实现更细粒度的访问控制。 +--- + +# 列级权限管理 + +从 v8.5.6 版本开始,TiDB 支持兼容 MySQL 的列级权限管理机制。通过列级权限,你可以在表级别为指定列授予或回收 `SELECT`、`INSERT`、`UPDATE`、`REFERENCES` 权限,从而实现更细粒度的数据访问控制。 + +> **注意:** +> +> 虽然 MySQL 语法允许 `REFERENCES(col_name)` 这种列级写法,但 `REFERENCES` 本身属于数据库/表级权限,用于外键相关的权限检查。因此,列级 `REFERENCES` 在 MySQL 中并不会真正生效。TiDB 的行为与 MySQL 保持一致。 + +## 语法 + +列级权限的授予和回收与表级权限类似,区别如下: + +- 列名列表位于**权限类型**后面,而不是位于**表名**后面。 +- 多个列名之间使用逗号(`,`)分隔。 + +```sql +GRANT priv_type(col_name [, col_name] ...) [, priv_type(col_name [, col_name] ...)] ... + ON db_name.tbl_name + TO 'user'@'host'; + +REVOKE priv_type(col_name [, col_name] ...) [, priv_type(col_name [, col_name] ...)] ... + ON db_name.tbl_name + FROM 'user'@'host'; +``` + +其中: + +* `priv_type` 支持 `SELECT`、`INSERT`、`UPDATE` 和 `REFERENCES`。 +* `ON` 后需要指定具体表,例如 `test.tbl`。 +* 同一条 `GRANT` 或 `REVOKE` 语句可以包含多个权限项,每个权限项都可以指定自己的列名列表。 + +例如,以下语句表示将 `col1`、`col2` 的 `SELECT` 权限和 `col3` 的 `UPDATE` 权限授予用户: + +```sql +GRANT SELECT(col1, col2), UPDATE(col3) ON test.tbl TO 'user'@'host'; +``` + +## 授予列级权限 + +以下示例将表 `test.tbl` 的 `col1` 和 `col2` 的 `SELECT` 权限授予用户 `newuser`,并将 `col3` 的 `UPDATE` 权限授予该用户: + +```sql +CREATE DATABASE IF NOT EXISTS test; +USE test; + +DROP TABLE IF EXISTS tbl; +CREATE TABLE tbl (col1 INT, col2 INT, col3 INT); + +DROP USER IF EXISTS 'newuser'@'%'; +CREATE USER 'newuser'@'%'; + +GRANT SELECT(col1, col2), UPDATE(col3) ON test.tbl TO 'newuser'@'%'; +SHOW GRANTS FOR 'newuser'@'%'; +``` + +``` ++---------------------------------------------------------------------+ +| Grants for newuser@% | ++---------------------------------------------------------------------+ +| GRANT USAGE ON *.* TO 'newuser'@'%' | +| GRANT SELECT(col1, col2), UPDATE(col3) ON test.tbl TO 'newuser'@'%' | ++---------------------------------------------------------------------+ +``` + +除了使用 `SHOW GRANTS`,你还可以通过查询 `INFORMATION_SCHEMA.COLUMN_PRIVILEGES` 查看列级权限信息。 + +## 回收列级权限 + +以下示例从用户 `newuser` 收回列 `col2` 的 `SELECT` 权限: + +```sql +REVOKE SELECT(col2) ON test.tbl FROM 'newuser'@'%'; +SHOW GRANTS FOR 'newuser'@'%'; +``` + +``` ++---------------------------------------------------------------+ +| Grants for newuser@% | ++---------------------------------------------------------------+ +| GRANT USAGE ON *.* TO 'newuser'@'%' | +| GRANT SELECT(col1), UPDATE(col3) ON test.tbl TO 'newuser'@'%' | ++---------------------------------------------------------------+ +``` + +## 列级权限访问控制示例 + +在授予或回收列级权限后,TiDB 会对 SQL 中引用的列进行权限检查。例如: + +* `SELECT` 语句:`SELECT` 列权限会影响 `SELECT` 列表以及 `WHERE`、`ORDER BY` 等子句中引用的列。 +* `UPDATE` 语句:`SET` 中被更新的列需要 `UPDATE` 列权限。在表达式、条件中被读取的列通常还需要 `SELECT` 列权限。 +* `INSERT` 语句:被写入的列需要 `INSERT` 列权限。`INSERT INTO t VALUES (...)` 等价于写入所有列。 + +以下示例中,用户 `newuser` 仅能查询 `col1`,并更新 `col3`: + +```sql +-- 以 newuser 登录执行 +SELECT col1 FROM tbl; +SELECT * FROM tbl; -- 报错(缺少 col2、col3 的 SELECT 列权限) + +UPDATE tbl SET col3 = 1; +UPDATE tbl SET col1 = 2; -- 报错(缺少 col1 的 UPDATE 列权限) + +UPDATE tbl SET col3 = col1; +UPDATE tbl SET col3 = col3 + 1; -- 报错(缺少 col3 的 SELECT 列权限) +UPDATE tbl SET col3 = col1 WHERE col1 > 0; +``` + +## 与 MySQL 的兼容性差异 + +TiDB 的列级权限整体与 MySQL 兼容,但在以下场景存在差异: + +| 场景 | TiDB | MySQL | +| :----------------------- | :-------------------------- | :---------------------------- | +| 收回用户未被授予的列级权限 | `REVOKE` 可以成功执行 | `REVOKE` 会报错 | +| 列裁剪与 `SELECT` 列权限检查的执行顺序 | 先检查 `SELECT` 列权限,再进行列裁剪。例如:执行 `SELECT a FROM (SELECT a, b FROM t) s` 需要同时拥有 `t.a` 和 `t.b` 的 `SELECT` 列权限。 | 先进行列裁剪,再检查 `SELECT` 列权限。例如:执行 `SELECT a FROM (SELECT a, b FROM t) s` 只需要 `t.a` 的 `SELECT` 列权限。 | + +### 视图场景的列裁剪与权限检查 + +在对视图进行 `SELECT` 权限检查时,MySQL 和 TiDB 存在以下差异: + +- MySQL 会先对视图内部查询做列裁剪,再检查内部表的列权限,因此在某些场景下检查相对宽松。 +- TiDB 不会在权限检查之前做列裁剪,因此可能需要额外的列权限。 + +```sql +-- 以 root 登录准备环境 +DROP USER IF EXISTS 'u'@'%'; +CREATE USER 'u'@'%'; + +DROP TABLE IF EXISTS t; +CREATE TABLE t (a INT, b INT, c INT, d INT); + +DROP VIEW IF EXISTS v; +CREATE SQL SECURITY INVOKER VIEW v AS SELECT a, b FROM t WHERE c = 0 ORDER BY d; + +GRANT SELECT ON v TO 'u'@'%'; + +-- 以 u 登录 +SELECT a FROM v; +-- MySQL:报错,缺少对 t.a、t.c、t.d 的访问权限 +-- TiDB:报错,缺少对 t.a、t.b、t.c、t.d 的访问权限 + +-- 以 root 登录 +GRANT SELECT(a, c, d) ON t TO 'u'@'%'; + +-- 以 u 登录 +SELECT a FROM v; +-- MySQL:成功(会将内部查询裁剪为 `SELECT a FROM t WHERE c = 0 ORDER BY d`) +-- TiDB:报错,缺少对 t.b 的访问权限 + +SELECT * FROM v; +-- MySQL:报错,缺少对 t.b 的访问权限 +-- TiDB:报错,缺少对 t.b 的访问权限 + +-- 以 root 登录 +GRANT SELECT(b) ON t TO 'u'@'%'; + +-- 以 u 登录 +SELECT * FROM v; +-- MySQL:成功 +-- TiDB:成功 +``` + +## 另请参阅 + +* [权限管理](/privilege-management.md) +* [`GRANT `](/sql-statements/sql-statement-grant-privileges.md) +* [`REVOKE `](/sql-statements/sql-statement-revoke-privileges.md) diff --git a/information-schema/information-schema.md b/information-schema/information-schema.md index 772e336423d0..344a212f9aed 100644 --- a/information-schema/information-schema.md +++ b/information-schema/information-schema.md @@ -18,7 +18,7 @@ Information Schema 提供了一种查看系统元数据的 ANSI 标准方法。 | [`COLLATIONS`](/information-schema/information-schema-collations.md) | 提供 TiDB 支持的排序规则列表。 | | [`COLLATION_CHARACTER_SET_APPLICABILITY`](/information-schema/information-schema-collation-character-set-applicability.md) | 说明哪些排序规则适用于哪些字符集。 | | [`COLUMNS`](/information-schema/information-schema-columns.md) | 提供所有表中列的列表。 | -| `COLUMN_PRIVILEGES` | TiDB 未实现,返回零行。 | +| `COLUMN_PRIVILEGES` | 汇总当前用户可见的列权限信息。 | | `COLUMN_STATISTICS` | TiDB 未实现,返回零行。 | | [`ENGINES`](/information-schema/information-schema-engines.md) | 提供支持的存储引擎列表。 | | `EVENTS` | TiDB 未实现,返回零行。 | @@ -36,14 +36,14 @@ Information Schema 提供了一种查看系统元数据的 ANSI 标准方法。 | `REFERENTIAL_CONSTRAINTS` | 提供有关 `FOREIGN KEY` 约束的信息。 | | `ROUTINES` | TiDB 未实现,返回零行。 | | [`SCHEMATA`](/information-schema/information-schema-schemata.md) | 提供与 `SHOW DATABASES` 命令类似的信息。 | -| `SCHEMA_PRIVILEGES` | TiDB 未实现,返回零行。 | +| `SCHEMA_PRIVILEGES` | 汇总当前用户可见的数据库权限信息。 | | `SESSION_STATUS` | TiDB 未实现,返回零行。 | | [`SESSION_VARIABLES`](/information-schema/information-schema-session-variables.md) | 提供与 `SHOW SESSION VARIABLES` 命令类似的功能。 | | [`STATISTICS`](/information-schema/information-schema-statistics.md) | 提供有关表索引的信息。 | | [`TABLES`](/information-schema/information-schema-tables.md) | 提供当前用户可见的表的列表。 类似于 `SHOW TABLES`。 | | `TABLESPACES` | TiDB 未实现,返回零行。 | | [`TABLE_CONSTRAINTS`](/information-schema/information-schema-table-constraints.md) | 提供有关主键、唯一索引和外键的信息。 | -| `TABLE_PRIVILEGES` | TiDB 未实现,返回零行。 | +| `TABLE_PRIVILEGES` | 汇总当前用户可见的表权限信息。 | | `TRIGGERS` | TiDB 未实现,返回零行。 | | [`USER_ATTRIBUTES`](/information-schema/information-schema-user-attributes.md) | 汇总用户的注释和属性信息。 | | [`USER_PRIVILEGES`](/information-schema/information-schema-user-privileges.md) | 汇总与当前用户相关的权限。 | diff --git a/mysql-compatibility.md b/mysql-compatibility.md index 017df64218c3..8566690bd570 100644 --- a/mysql-compatibility.md +++ b/mysql-compatibility.md @@ -34,7 +34,6 @@ TiDB 高度兼容 MySQL 协议,以及 MySQL 5.7 和 MySQL 8.0 常用的功能 * MySQL 追踪优化器 * XML 函数 * X-Protocol [#1109](https://github.com/pingcap/tidb/issues/1109) -* 列级权限 [#9766](https://github.com/pingcap/tidb/issues/9766) * `XA` 语法(TiDB 内部使用两阶段提交,但并没有通过 SQL 接口公开) * `CREATE TABLE tblName AS SELECT stmt` 语法 [#4754](https://github.com/pingcap/tidb/issues/4754) * `CHECK TABLE` 语法 [#4673](https://github.com/pingcap/tidb/issues/4673) diff --git a/privilege-management.md b/privilege-management.md index 5f77eea4dbb6..f4acf613cbc5 100644 --- a/privilege-management.md +++ b/privilege-management.md @@ -30,6 +30,8 @@ GRANT SELECT ON test.* TO 'xxx'@'%'; GRANT ALL PRIVILEGES ON *.* TO 'xxx'@'%'; ``` +从 v8.5.6 版本开始,TiDB 支持兼容 MySQL 的列级权限管理机制。你可以在表级别为指定列授予或回收 `SELECT`、`INSERT`、`UPDATE`、`REFERENCES` 权限。更多信息参见[列级权限管理](/column-privilege-management.md)。 + 默认情况下,如果指定的用户不存在,[`GRANT`](/sql-statements/sql-statement-grant-privileges.md) 语句将报错。该行为受 [SQL 模式](/system-variables.md#sql_mode)中的 `NO_AUTO_CREATE_USER` 控制。 ```sql @@ -514,7 +516,7 @@ SELECT * FROM INFORMATION_SCHEMA.USER_PRIVILEGES WHERE grantee = "'root'@'%'"; - `mysql.user`:用户账户,全局权限 - `mysql.db`:数据库级别的权限 - `mysql.tables_priv`:表级别的权限 -- `mysql.columns_priv`:列级别的权限,当前暂不支持 +- `mysql.columns_priv`:列级别的权限(从 v8.5.6 开始支持) 这几张表包含了数据的生效范围和权限信息。例如,`mysql.user` 表的部分数据: diff --git a/sql-statements/sql-statement-grant-privileges.md b/sql-statements/sql-statement-grant-privileges.md index 8d8d160e0b15..3674be7cda7b 100644 --- a/sql-statements/sql-statement-grant-privileges.md +++ b/sql-statements/sql-statement-grant-privileges.md @@ -101,7 +101,7 @@ SHOW GRANTS FOR 'newuser'; ## MySQL 兼容性 * 与 MySQL 类似,`USAGE` 权限表示登录 TiDB 服务器的能力。 -* 目前不支持列级权限。 +* 从 v8.5.6 版本开始,TiDB 支持兼容 MySQL 的列级权限管理机制。你可以在表级别为指定列授予或回收 `SELECT`、`INSERT`、`UPDATE`、`REFERENCES` 权限。更多信息参见[列级权限管理](/column-privilege-management.md)。 * 与 MySQL 类似,不存在 `NO_AUTO_CREATE_USER` sql 模式时,`GRANT` 语句将在用户不存在时自动创建一个空密码的新用户。删除此 sql-mode(默认情况下已启用)会带来安全风险。 * `GRANT ` 语句执行成功后,在 TiDB 中语句执行的结果会在当前连接立即生效,而 [MySQL 中部分权限的结果需要等到之后的连接才生效](https://dev.mysql.com/doc/refman/8.0/en/privilege-changes.html)。见 [TiDB #39356](https://github.com/pingcap/tidb/issues/39356)。 diff --git a/sql-statements/sql-statement-revoke-privileges.md b/sql-statements/sql-statement-revoke-privileges.md index 8f32a86ccd88..c16f982b684d 100644 --- a/sql-statements/sql-statement-revoke-privileges.md +++ b/sql-statements/sql-statement-revoke-privileges.md @@ -7,6 +7,8 @@ summary: TiDB 数据库中 REVOKE 的使用概况。 `REVOKE ` 语句用于删除已有用户的权限。执行 `REVOKE ` 语句需要拥有分配的权限,并且拥有 `GRANT OPTION` 权限。 +从 v8.5.6 版本开始,TiDB 支持兼容 MySQL 的列级权限管理机制,你可以在 `REVOKE` 中指定列名列表,例如,`REVOKE SELECT(col2) ON test.tbl FROM 'user'@'host';`。更多信息参见[列级权限管理](/column-privilege-management.md)。 + ## 语法图 ```ebnf+diagram