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 新功能加入,社区版没有这个功能。
参考文档
- 8.5.3.2 Using MySQL Enterprise Dynamic Data Masking Policies
- 15.1.21 CREATE MASKING POLICY Statement
- 8.5.2.1 MySQL Enterprise Data Masking Component Installation
- 8.5.2.3 MySQL Enterprise Data Masking Component Function Reference
- Oracle MySQL blog: MySQL Enterprise Edition 9.7 の 動的データマスキング
组件与安装
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。
出问题时的排查顺序
object_policy、component_masking、component_masking_functions是否都装了,CREATE MASKING POLICY报错通常卡在这一步。- 旧的
data_masking插件是否已卸载,残留会冲突。 - 若用了专用 schema,
component_masking.masking_database是否SET PERSIST_ONLY并重启生效。 - 当前用户是否有
MANAGE_DATA_MASKING_POLICY。 - 目标列是否命中上面那张表里的任意一条限制,尤其是索引、histogram、CHECK、分区键。
EXPLAIN看执行计划,确认 masked 列上没有可用的索引路径。- 主库装完组件后,每个 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,多租户场景下策略数量会不会爆炸;以及策略变更时正在执行的查询拿到的是新策略还是旧策略。这些都得等实际压测和灰度才能回答。