编程 MySQL 9.7 企业版动态脱敏:列上挂 MASKING POLICY 后,WHERE、索引和导出都得重算

2026-10-24 00:04:04

MySQL 9.7 企业版 DDM:在查询解析阶段把脱敏列替换成 CASE 表达式

MySQL 9.7 Enterprise 的动态数据脱敏(Dynamic Data Masking,DDM)主要作用于引擎层的读路径改写。查询解析阶段,masked 列的引用被替换成该列 masking policy 里的 CASE 表达式,由服务端强制执行。它不改变存储、不接管写入,也不替代应用层字段加密和日志脱敏——写入表格的仍是明文,只是查询返回时按当前用户或有效角色的权限决定是否返回明文。

DDM 在 MySQL 9.7.0 LTS 作为 Enterprise Edition 新功能加入,社区版没有这个功能。

参考文档

组件与安装

masking policy 定义由 object_policy 组件存储,组件未安装时 CREATE MASKING POLICY 直接报错。核心是两个组件:component_masking(核心服务)和 component_masking_functions(暴露为 loadable functions)。

安装脚本 masking_functions_install.sql 放在 MySQL 安装目录的 share 下:

mysql -u root -p -D mysql < [path/]masking_functions_install.sql

如果需要专用 schema 存 masking_dictionaries,可以这么装。理由有三条:mysql schema 不被 mysqldump 逻辑备份;专用 schema 便于外发复制;不需要 mysql schema 权限。

CREATE DATABASE IF NOT EXISTS mask_db;
mysql -u root -p -D mask_db < [path/]masking_functions_install.sql
SET PERSIST_ONLY component_masking.masking_database=mask_db;

设置完需要重启。另外旧插件 data_masking 必须先卸载,否则冲突;主库安装了组件,所有 replica 也要安装,避免复制问题。

策略语法

CREATE MASKING POLICY [IF NOT EXISTS] policy_name (policy_argument)
CASE WHEN CURRENT_ROLE_IN(role_list) | CURRENT_USER_IN(user_list)
     THEN policy_argument
     ELSE masking_expr(policy_argument)
END;

约束:

  • policy 名与参数都不能为空、不能以空格结尾、不超过 64 字符。
  • 只有持有 MANAGE_DATA_MASKING_POLICY 权限的用户能创建。
  • gatekeeper 只有 CURRENT_ROLE_IN 和 CURRENT_USER_IN 两个。
  • masking 部分必须是关于 policy_argument 的 CASE 表达式;不能引用其它列;不能含子查询、窗口函数、表引用、用户变量、非确定性构造。

示例:

CREATE MASKING POLICY mask_password1(pass_column)
CASE WHEN CURRENT_ROLE_IN('admin')
     THEN pass_column
     ELSE sha2(pass_column, 256)
END;

挂到列上

四种写法,建表和改表都覆盖:

CREATE TABLE user_data(id INT NOT NULL, ssn INT NOT NULL MASKING POLICY mask_ssn);
ALTER TABLE t1 ADD COLUMN j VARCHAR(256) MASKING POLICY mask_ssn;
ALTER TABLE t1 MODIFY COLUMN j VARCHAR(256) MASKING POLICY mask_ssn;
ALTER TABLE t1 CHANGE COLUMN j j VARCHAR(256) MASKING POLICY mask_ssn;

限制与后果对照

限制直接后果
不能挂到 GENERATED(VIRTUAL/STORED)列派生列本身无法脱敏,只能在读取侧另做处理
若有 GENERATED 列引用了该列,则不能挂要么放弃脱敏,要么先把生成列改成普通列由应用维护
不能挂到已建索引的列挂之前得先 DROP INDEX;该列上的过滤、关联失去索引
不能挂到带 histogram 的列需先清掉统计信息,执行计划估算随之退化
不能挂到被表上任何 CHECK 约束引用的列先删约束,再挂策略
不能挂到分区键(含子分区键)中的列分区裁剪用的列与脱敏列不能是同一个
可挂到带 DEFAULT 表达式的列,但 DEFAULT 表达式不能引用带掩码策略的列默认值逻辑要绕开敏感列
视图/存储函数/存储过程在 SQL SECURITY INVOKER 下按调用者权限判断无权限读明文时直接返回错误,不是静默降级为掩码值

最后一条容易被忽略:报错发生在执行期,不在创建期,所以建视图时一切正常,换个账号跑才炸。

掩码函数

component_masking_functions 提供,需要 MASKING_DICTIONARIES_ADMIN 的部分另计。

通用:

  • mask_inner(str, left, right[, char])、mask_outer(str, left, right[, char]),默认用 X,可换字符,支持任意字符集。示例:mask_inner("かすみがうら市",3,1) 得到 かすみXXX市。

专用:mask_pan()、mask_pan_relaxed()、mask_ssn()、mask_uk_nin()、mask_canada_sin()、mask_iban()、mask_uuid()。

生成随机值:gen_rnd_email()、gen_rnd_us_phone()、gen_rnd_ssn()、gen_rnd_pan()、gen_rnd_iban()、gen_rnd_uuid()、gen_range()、gen_blocklist()、gen_dictionary()。

字典管理:masking_dictionary_term_add/remove、masking_dictionary_remove、masking_dictionaries_flush。

出问题时的排查顺序

  1. object_policy、component_masking、component_masking_functions 是否都装了,CREATE MASKING POLICY 报错通常卡在这一步。
  2. 旧的 data_masking 插件是否已卸载,残留会冲突。
  3. 若用了专用 schema,component_masking.masking_database 是否 SET PERSIST_ONLY 并重启生效。
  4. 当前用户是否有 MANAGE_DATA_MASKING_POLICY。
  5. 目标列是否命中上面那张表里的任意一条限制,尤其是索引、histogram、CHECK、分区键。
  6. EXPLAIN 看执行计划,确认 masked 列上没有可用的索引路径。
  7. 主库装完组件后,每个 replica 是否同步安装。

文档没有明说、按机制推导需要自测的部分

以下几点在参考手册里没有逐条给出实测结论,属于从「解析阶段替换成 CASE 表达式」这一实现推出来的行为,上生产前建议自己跑一遍:

  • masked 列上的 WHERE / ORDER BY / GROUP BY / 聚合都作用在掩码值上。对 masked 列做等值过滤,只能匹配到掩码后的值,传明文进去大概率匹配不到行。按机制推导,需自测。
  • 导出通道:SELECT ... INTO OUTFILE、以被脱敏账号执行 mysqldump,拿到的是掩码后的数据。这条路径可以当作生产到测试的静态脱敏通道用。按机制推导,需自测。
  • 备份与复制路径:DDM 是读路径改写,页级/物理备份拿到的应是原始明文,binlog 里记录的语句形态也需要实测确认;但 replica 侧必须安装组件这一点文档明确写了,否则会出现复制问题。按机制推导,需自测。

局限

DDM 解决的是「同一个库、同一张表,不同账号看到不同结果」,它不解决静态数据落盘、备份文件、导出文件和 binlog 里的明文问题,也不改变写路径。限定条件那一串——不能建索引、不能是分区键、不能是生成列、不能被 CHECK 引用——决定了它在既有表结构上不是随时能落地,经常要先动表结构再谈脱敏。列级粒度加上单列策略,跨列组合条件(比如「同时满足 A 和 B 才放行」)表达不了。

另外,社区版没有这个功能,这一点是选型时的硬性取舍:DDM 只在 Enterprise Edition 提供,没法通过插件或降级方案在社区版复现同等语义。真要在社区版做读路径脱敏,只能退回到视图 + 权限,或者应用层改写,两者的维护成本和 DDM 不在一个量级,但至少不需要更换发行版。

还有几处没想清楚:掩码值参与排序时,ORDER BY 的语义和业务预期怎么对齐;CURRENT_ROLE_IN 与 CURRENT_USER_IN 之外没有更细的 gatekeeper,多租户场景下策略数量会不会爆炸;以及策略变更时正在执行的查询拿到的是新策略还是旧策略。这些都得等实际压测和灰度才能回答。

推荐文章

程序员茄子在线接单