通过Spark SQL实现行级过滤和列脱敏

更新时间:
复制 MD 格式

本文介绍如何在AnalyticDB MySQL版Spark中通过行级过滤策略(Row Filter Policy)和列脱敏策略(Column Mask Policy)实现细粒度的数据访问控制。

功能概述

AnalyticDB MySQL版Spark支持行级过滤和列脱敏两种数据访问控制能力,帮助您实现细粒度的数据安全保护。

能力

作用

生效方式

行级过滤(Row Filter)

限制用户只能看到表中符合条件的数据行。

被绑定用户执行SELECT时,系统自动在查询上叠加过滤条件(等价于追加WHERE谓词)。

列脱敏(Column Mask)

限制用户看到敏感列的脱敏后的值(如掩码、哈希、置空)。

被绑定用户执行SELECT时,系统自动对目标列应用脱敏表达式。

核心概念:

  • 策略(Policy):管理员定义的过滤条件或脱敏表达式,作用于某张表(行过滤)或某张表的某一列(列脱敏)。策略统一存储在元数据服务中。

  • 绑定(Binding):策略与用户的关联关系。策略创建后默认对所有用户不生效,必须显式绑定(BIND)到目标用户后才生效。

  • 功能开关:行过滤与列脱敏各自有独立的Spark配置开关,默认均为关闭,需显式开启。

行级过滤和列脱敏相互独立,可单独使用,也可在同一张表上叠加使用。

前提条件

版本要求

Spark能力依赖AnalyticDB MySQL升级到特定内核版本。低于3.2.8版本的集群需要先进行集群升级。

权限要求

  • 策略管理DDL(CREATE、UPDATE、DROP、BIND、UNBIND、SHOW)必须由管理员账号执行。普通用户执行策略DDL会收到AccessDenied错误。

  • 普通用户无需任何额外授权即可被策略约束:策略绑定到谁,谁就在查询时自动受约束。

用户身份

  • 策略绑定与生效判定基于ADB用户名(即登录账号)。BIND ... TO '<user>'中的用户名必须与目标用户的ADB登录账号完全一致。

  • 查询时系统自动从登录态解析当前用户身份,再匹配该用户已绑定的策略。身份解析失败时查询会被中止(fail-closed),不会在无保护状态下放行。

连接方式

通过JDBC(Beeline、DBeaver或应用程序)连接Spark交互式资源组后即可使用,示例如下:

jdbc:hive2://<spark-endpoint>:10000/<database>?resource_group=<资源组名>

行级过滤(Row Filter)

适用场景

  • 多租户数据隔离:每个租户的分析师只能看到本租户的数据行(如tenant_id = 'T001')。

  • 区域或组织级数据可见性:区域运营只能看到本区域数据(如region = 'APAC')。

  • 敏感记录保护:普通员工不能看到高管、薪酬超阈值等敏感记录(如salary < 50000)。

  • 合规审计要求:法规要求特定人群只能访问脱敏子集的数据行。

SQL语句参考

操作

语法

说明

创建策略

CREATE ROW FILTER POLICY [IF NOT EXISTS] <策略名> ON <库.表> USING (<过滤条件>);

创建后不绑定任何用户,不生效。带IF NOT EXISTS时,策略已存在则静默跳过,不会覆盖原有条件;不带时同名策略已存在则报错。

更新条件

UPDATE ROW FILTER POLICY <策略名> USING (<新条件>);

更新过滤条件,对所有已绑定用户立即生效。

删除策略

DROP ROW FILTER POLICY <策略名>;

级联删除该策略的所有用户绑定关系。

查看策略

SHOW ROW FILTER POLICIES [IN <库名>];

列出全部或指定库下的行过滤策略。输出列:PolicyName、Database、Table、Condition。

绑定用户

BIND ROW FILTER POLICY <策略名> TO '<用户1>'[, '<用户2>', ...];

策略对绑定用户生效。支持一次绑定多个用户。

解绑用户

UNBIND ROW FILTER POLICY <策略名> FROM '<用户1>'[, '<用户2>', ...];

解除后用户立即恢复全量可见。

查看绑定

SHOW ROW FILTER BINDINGS [IN <库名> | FOR POLICY <策略名> | FOR USER '<用户>'];

按库、按策略或按用户查看绑定关系。输出列:PolicyName、Database、Table、Condition、User。

使用示例

以下示例基于演示表sec_e2e.employees:

-- 管理员准备演示数据
CREATE DATABASE IF NOT EXISTS sec_e2e;
CREATE TABLE IF NOT EXISTS sec_e2e.employees (
  id INT, name STRING, region STRING, department STRING, salary DOUBLE, phone STRING
) USING parquet;

INSERT INTO sec_e2e.employees VALUES
  (1, 'Alice', 'APAC', 'HR',          75000.0,  '13812341234'),
  (2, 'Bob',   'EU',   'Engineering', 95000.0,  '13956785678'),
  (3, 'Carol', 'US',   'Finance',     85000.0,  '13790129012'),
  (4, 'David', 'APAC', 'Engineering', 105000.0, '13634563456'),
  (5, 'Eve',   'EU',   'HR',          70000.0,  '13578907890');

场景:APAC区域分析师global_read只能看到APAC区域的员工数据。

-- ① 管理员创建行过滤策略
CREATE ROW FILTER POLICY rf_apac ON sec_e2e.employees USING (region = 'APAC');

-- ② 管理员将策略绑定到目标用户
BIND ROW FILTER POLICY rf_apac TO 'global_read';

-- ③ 管理员核对策略与绑定关系
SHOW ROW FILTER POLICIES IN sec_e2e;
-- PolicyName | Database | Table     | Condition
-- rf_apac    | sec_e2e  | employees | region = 'APAC'

SHOW ROW FILTER BINDINGS FOR POLICY rf_apac;
-- PolicyName | Database | Table     | Condition        | User
-- rf_apac    | sec_e2e  | employees | region = 'APAC'  | global_read

被绑定用户global_read查询验证(用户侧无需改写任何SQL):

SELECT * FROM sec_e2e.employees;
-- 仅返回 region = 'APAC' 的两行(id=1 Alice、id=4 David),
-- EU / US 的行对该用户完全不可见

过滤条件支持任意合法的SQL谓词表达式,包括函数与UDF:

-- 多条件组合
CREATE ROW FILTER POLICY rf_dept ON sec_e2e.employees
  USING (department = 'HR' AND salary > 71000.0);

-- 使用自定义函数(UDF)作为过滤条件
CREATE FUNCTION IF NOT EXISTS is_hr_with_valid_int
  AS 'com.aliyun.adb.udf.IsHrWithValidInt' USING JAR 'oss://<bucket>/hr-udf-1.0.0.jar';

CREATE ROW FILTER POLICY rf_hr_high_salary ON sec_e2e.employees
  USING (is_hr_with_valid_int(department, CAST(phone AS BIGINT)) AND salary > 71000.0);

策略维护:

-- 更新过滤条件(对所有已绑定用户立即生效)
UPDATE ROW FILTER POLICY rf_apac USING (region = 'EU');

-- 解绑用户(该用户立即恢复全量可见)
UNBIND ROW FILTER POLICY rf_apac FROM 'global_read';

-- 删除策略(级联清除所有绑定关系)
DROP ROW FILTER POLICY rf_apac;

支持幂等创建,适合脚本化和自动化管理:

-- 策略已存在时静默跳过,不报错、不覆盖原条件
CREATE ROW FILTER POLICY IF NOT EXISTS rf_apac ON sec_e2e.employees USING (region = 'APAC');

生效范围与防绕过

行过滤在查询分析阶段自动注入,对用户透明且无法通过SQL写法绕过:

  • 子查询:SELECT * FROM (SELECT * FROM sec_e2e.employees) t,内层扫描同样被过滤。

  • CTE(WITH子句):WITH cte AS (SELECT * FROM sec_e2e.employees) SELECT * FROM cte,同样被过滤。

  • 视图(含临时视图):创建临时视图后查询,同样被过滤。

  • JOIN:过滤条件只作用于绑定了策略的表,未绑定策略的关联表不受影响。

写入保护(禁写)

开启行过滤(rowFilter.enabled=true)后,禁写保护默认同时开启(blockWrites.enabled=true):

  • 当前用户在某表上绑定了行过滤策略时,禁止其对该表执行任何数据修改操作,包括INSERT(INTO/OVERWRITE)、UPDATE、DELETE、MERGE、TRUNCATE TABLE、LOAD DATA、REPLACE TABLE和ALTER TABLE ... DROP PARTITION。

  • 违规写入会报错并中止。

不受禁写影响的场景:

  • 未绑定策略的用户(含管理员)对同一张表的写入照常。

  • CREATE TABLE ... AS SELECT(CTAS)写入新表放行(新表不存在既有策略绑定)。

  • INSERT INTO 无策略表 SELECT ... FROM 有策略表放行,写入目标表无策略,读取源表时行过滤照常生效。

说明

设计动机:被行过滤约束的用户只能看到部分行,若允许其写入,可能破坏其不可见数据的一致性(如INSERT OVERWRITE会用可见的部分数据覆盖全表)。存量作业若确需保留绑定行过滤但允许写的旧行为,可设置spark.adb.security.rowFilter.blockWrites.enabled=false(不推荐)。

限制说明

  • 默认关闭:spark.adb.security.rowFilter.enabled默认为false,未开启时策略不生效、查询返回明文(fail-open)。开关为静态配置,需在资源组或作业启动配置中设置。

  • 仅覆盖内置Catalog:行过滤仅对spark_catalog(ADB内置元数据目录)下的表生效。通过外部Catalog(如external-hive、paimon等V2 Catalog)访问的表不在策略范围内。

  • 同一用户在同一张表上只允许绑定一个行过滤策略:重复绑定第二个策略时,BIND语句直接报错。如需切换策略,先UNBIND旧策略再BIND新策略。

  • 一张表可以创建多个行过滤策略(策略名需唯一),用于绑定给不同用户群,但同一用户只能命中其中一条。

  • 过滤条件在创建时做语法预校验,但条件中引用的列或函数在查询时才真正解析。若策略创建后表结构变更导致条件无法解析,被绑定用户的查询将报错中止(fail-closed),而不是放行。

  • 策略变更的生效时间:执行DDL的会话立即生效;其他已运行的Spark应用因存在策略缓存(默认5分钟TTL),最长延迟5分钟生效。

  • 跨引擎冲突:若同一张表在其他引擎(XIHE)上已存在行过滤策略,Spark侧创建策略会报错,且该冲突无法在Spark侧自行解除,需联系管理员在原引擎侧处理。

  • 元数据服务未配置时功能降级:环境未配置元数据服务端点时,行过滤自动降级为无操作(普通查询不受影响,但安全保护不生效)。

列脱敏(Column Mask)

适用场景

  • 个人隐私信息(PII)保护:手机号、身份证号、邮箱等对普通分析人员脱敏展示(如138****1234)。

  • 薪酬等敏感字段保护:普通员工看不到真实薪资,只能看到哈希值或掩码。

  • 合规要求:法规要求敏感字段在非授权场景下不可见明文。

  • 数据共享或外包分析:外部协作账号可以使用数据做分析,但敏感列一律脱敏。

SQL语句参考

列脱敏DDL与行过滤同构,将ROW FILTER替换为MASKING,并额外指定目标列:

操作

语法

说明

创建策略

CREATE MASKING POLICY [IF NOT EXISTS] <策略名> ON <库.表> COLUMN <列名> USING (<脱敏表达式>);

创建后不绑定任何用户,不生效。IF NOT EXISTS语义与行过滤一致(已存在则静默跳过,不覆盖原表达式)。

更新表达式

UPDATE MASKING POLICY <策略名> USING (<新表达式>);

更新脱敏表达式,对所有已绑定用户立即生效。

删除策略

DROP MASKING POLICY <策略名>;

级联删除所有绑定关系。

查看策略

SHOW MASKING POLICIES [IN <库名>];

列出全部(或指定库下的)脱敏策略。输出列:PolicyName、Database、Table、Column、MaskExpression。

绑定用户

BIND MASKING POLICY <策略名> TO '<用户1>'[, '<用户2>', ...];

策略对绑定用户生效。

解绑用户

UNBIND MASKING POLICY <策略名> FROM '<用户1>'[, '<用户2>', ...];

解除后用户立即恢复看到原值。

查看绑定

SHOW MASKING BINDINGS [IN <库名> | FOR POLICY <策略名> | FOR USER '<用户>'];

按库、按策略或按用户查看绑定关系。输出列:PolicyName、Database、Table、Column、MaskExpression、User。

使用示例

沿用行级过滤示例中的sec_e2e.employees表,目标用户为global_read。

部分脱敏(保留前3位与后4位的手机号掩码):

CREATE MASKING POLICY cm_phone ON sec_e2e.employees COLUMN phone
  USING (concat(substr(phone, 1, 3), '****', substr(phone, 8, 4)));

BIND MASKING POLICY cm_phone TO 'global_read';

global_read查询效果:

SELECT id, name, phone FROM sec_e2e.employees;
-- id | name  | phone
-- 1  | Alice | 138****1234
-- 2  | Bob   | 139****5678
-- ...(phone 列全部按掩码规则展示,明文不可见)

哈希脱敏(不可逆摘要,可用于关联分析但不泄露原值):

CREATE MASKING POLICY cm_salary ON sec_e2e.employees COLUMN salary
  USING (md5(cast(salary AS STRING)));

BIND MASKING POLICY cm_salary TO 'global_read';
-- global_read查询salary列返回32位十六进制串,如'5f4dcc3b5aa765d61d8327deb882****'

全脱敏(常量掩码):

CREATE MASKING POLICY cm_name ON sec_e2e.employees COLUMN name
  USING ('********');

BIND MASKING POLICY cm_name TO 'global_read';
-- global_read查询name列全部显示'********'

NULL脱敏(完全隐藏列值):

CREATE MASKING POLICY cm_region ON sec_e2e.employees COLUMN region
  USING (CAST(NULL AS STRING));

BIND MASKING POLICY cm_region TO 'global_read';
-- global_read查询region列全部返回NULL

多列同时脱敏:

一张表可创建多条脱敏策略分别作用于不同列,未设置脱敏的列保持原值。

SELECT * FROM sec_e2e.employees WHERE id = 1;
-- global_read 视角:
-- id=1, name='********', region=NULL, department='HR'(原值),
-- salary=<32位md5>, phone='138****1234'

策略维护:

-- 更新脱敏表达式
UPDATE MASKING POLICY cm_phone USING ('********');

-- 查看某用户绑定的全部脱敏策略
SHOW MASKING BINDINGS FOR USER 'global_read';

-- 解绑后该用户立即恢复看到原值
UNBIND MASKING POLICY cm_phone FROM 'global_read';

-- 删除策略(级联清除绑定)
DROP MASKING POLICY cm_phone;

生效范围与防绕过

与行过滤一致,列脱敏在查询分析阶段自动注入,子查询、CTE、视图(含临时视图)均无法绕过;JOIN场景下仅对绑定策略的表的对应列脱敏。

限制说明

  • 默认关闭:spark.adb.security.columnMask.enabled默认为false,未开启时策略不生效、查询返回明文(fail-open)。

  • 仅覆盖内置Catalog:仅对spark_catalog下的表生效,外部Catalog表不在策略范围内。

  • 脱敏表达式仅支持纯标量表达式,以下写法会被拒绝:

    • 聚合函数(如sum(col)、max(col))。

    • 窗口函数或窗口表达式(如row_number() OVER (...))。

    • DISTINCT、FILTER修饰的函数调用。

    • 三段式以上的函数名(仅支持fn或db.fn两种形式)。

    • 引用当前表以外的列(表达式中引用的列必须属于策略所在表)。

  • 同一用户在同一张表的同一列上,查询时只允许命中一条脱敏策略。若因历史原因同一(表, 列, 用户)存在多条绑定,查询将报错中止(fail-closed),需管理员清理至恰好保留一条。

  • 脱敏后列的数据类型可能变化:输出列名保持不变,但类型由脱敏表达式决定。下游报表或应用若依赖原始列类型,需提前评估。

  • 列脱敏只影响读取,不拦截写入:被绑定用户的INSERT等写操作照常执行(写入的是原值),但其读回的数据按策略脱敏。若需要绑定即禁写的强保护,请使用行过滤。

  • 表达式在创建时做语法预校验,表达式中引用的列或函数在查询时才真正解析绑定。若创建后表结构变更导致表达式无法解析,被绑定用户的查询将报错中止(fail-closed)。

  • 策略变更的生效时间:执行DDL的会话立即生效;其他已运行的Spark应用最长延迟5分钟(策略缓存TTL)生效。

  • 跨引擎冲突:若同一张表的同一列在其他引擎(XIHE)上已存在脱敏策略,Spark侧创建策略会报错,需联系管理员在原引擎侧处理。

行过滤和列脱敏组合使用

同一张表、同一用户可同时施加两种保护:先按行裁剪可见范围,再对可见行中的敏感列脱敏。

-- 管理员:分别创建并绑定两种策略(需同时开启两个功能开关)
CREATE ROW FILTER POLICY rf_apac ON sec_e2e.employees USING (region = 'APAC');
BIND ROW FILTER POLICY rf_apac TO 'global_read';

CREATE MASKING POLICY cm_phone ON sec_e2e.employees COLUMN phone
  USING (concat(substr(phone, 1, 3), '****', substr(phone, 8, 4)));
BIND MASKING POLICY cm_phone TO 'global_read';

global_read查询效果:

SELECT id, name, region, phone FROM sec_e2e.employees;
-- id | name  | region | phone
-- 1  | Alice | APAC   | 138****1234
-- 4  | David | APAC   | 136****3456
-- (仅APAC行可见,且可见行的phone已脱敏)

执行顺序:行过滤先于列脱敏执行,过滤条件基于列的原始值求值,即使过滤列本身也被脱敏:

-- 极端示例:把过滤列region本身脱敏为NULL
CREATE MASKING POLICY cm_region ON sec_e2e.employees COLUMN region
  USING (CAST(NULL AS STRING));
BIND MASKING POLICY cm_region TO 'global_read';

SELECT id, region FROM sec_e2e.employees;   -- global_read执行
-- id | region
-- 1  | NULL
-- 4  | NULL
-- 行过滤region='APAC'在原始值上求值,仍正确返回id=1/4两行;
-- 展示时region列被脱敏为NULL。

两个特性相互独立:UNBIND或DROP脱敏策略不影响行过滤继续生效,反之亦然。

安全语义与可靠性

语义

说明

Fail-closed(查询期)

功能开关开启且元数据服务可用时:策略加载失败、条件或表达式解析失败、用户身份解析失败、同列多策略冲突等情况,查询一律报错中止,绝不放行明文。

Fail-open(开关层)

功能开关关闭时策略完全不生效(返回明文)。生产环境务必显式开启开关。

降级契约

元数据服务端点未配置时,行过滤和列脱敏自动降级为无操作:不产生额外远程调用,普通查询不受影响(但保护同样不生效)。

错误信息防泄露

查询期报错信息只包含策略名、表名、用户名,不携带过滤条件或脱敏表达式的具体内容,防止通过报错逆向推断策略。

底层数据不变

两种策略都只改变查询结果的呈现,不修改、不加密底层存储的数据。

常见问题

现象或错误信息

原因

处理建议

策略已绑定,但用户仍能看到全量或明文数据

功能开关未开启(默认关闭)

在资源组或作业配置中设置spark.adb.security.rowFilter.enabled=true和(或)spark.adb.security.columnMask.enabled=true并重启生效。

创建策略报already exists

同名策略已存在

改用IF NOT EXISTS幂等创建,或先DROP旧策略。

BIND报User already has a row filter policy on this table

同一用户在同一张表已绑定其他行过滤策略

先UNBIND旧策略,再BIND新策略。

普通用户执行策略DDL报AccessDenied

策略DDL仅管理员可执行

使用管理员账号执行策略管理语句。

写入报Data modification on table ... is not allowed

该用户在此表绑定了行过滤策略,触发禁写保护

属预期行为。如业务确需写入:由管理员解绑策略,或评估设置spark.adb.security.rowFilter.blockWrites.enabled=false。

查询报策略条件无法解析(fail-closed)

策略条件引用的列或函数已失效(如表结构变更)

联系管理员修正策略条件或DROP策略。

策略修改后部分作业未立即生效

其他运行中的Spark应用存在策略缓存

默认5分钟TTL后自动生效;执行DDL的会话本身立即生效。

创建策略报XIHE冲突类错误

同表或同列在其他引擎已存在策略

跨引擎冲突无法在Spark侧解除,联系管理员在原引擎侧处理。

限制汇总速查

限制项

行过滤

列脱敏

默认关闭,需显式开启静态开关(启动时生效)

✅

✅

仅覆盖spark_catalog内置目录的表

✅

✅

策略DDL仅管理员可执行

✅

✅

策略创建后必须BIND才生效;DROP级联清除绑定

✅

✅

同一用户同一表仅允许一条绑定

✅(BIND时拦截)

同一列查询时仅允许命中一条(冲突即fail-closed报错)

表达式限制

任意合法SQL谓词(支持UDF)

仅纯标量表达式;禁聚合/窗口/DISTINCT/FILTER;函数名限fn/db.fn;列必须属于本表

对写操作的影响

绑定用户默认禁写(可配置关闭)

不拦截写入,仅影响读取

子查询/CTE/视图/JOIN

生效,不可绕过

生效,不可绕过

解析失败/加载失败

fail-closed中止查询

fail-closed中止查询

策略缓存

默认5分钟TTL,DDL会话即时生效

同左

跨引擎(XIHE)同表/同列冲突

创建报错,不可自愈

创建报错,不可自愈