列级权限管理
从 v8.5.6 版本开始,TiDB 支持兼容 MySQL 的列级权限管理机制。通过列级权限,你可以在指定表上对指定列授予或回收 SELECT、INSERT、UPDATE、REFERENCES 权限,从而实现更细粒度的数据访问控制。
语法
列级权限的授予和回收语法与表级权限类似,区别如下:
- 列名列表写在权限类型后面,而不是写在表名后面。
- 多个列名之间使用逗号(
,)分隔。
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 权限授予用户:
GRANT SELECT(col1, col2), UPDATE(col3) ON test.tbl TO 'user'@'host';
授予列级权限示例
以下示例将表 test.tbl 中 col1 和 col2 的 SELECT 权限授予用户 newuser,并将 col3 的 UPDATE 权限授予该用户:
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 权限:
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:
-- 以 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 兼容,但在以下场景存在差异:
视图场景的列裁剪与权限检查
在对视图进行 SELECT 权限检查时,MySQL 和 TiDB 存在以下差异:
- MySQL 会先对视图内部查询做列裁剪,再检查内部表的列权限,因此在某些场景下检查相对宽松。
- TiDB 不会在权限检查之前做列裁剪,因此可能需要额外的列权限。
-- 以 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:成功