技术指南:设计原理、SQL 接口与实施细节)
TiDB 服务端列级数据脱敏Column-Level Data Masking技术指南设计原理、SQL 接口与实施细节【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb导读本文基于 TiDB 仓库中的设计文档《Proposal: Server-side Column-Level Data Masking》设计文档系统介绍 TiDB 原生服务端列级数据脱敏能力的设计与实现。该功能将脱敏定义为绑定到表列、在查询结果返回时求值的策略覆盖角色/用户感知的动态脱敏基于 SQL 表达式、内置脱敏函数MASK_PARTIAL/MASK_FULL/MASK_NULL/MASK_DATE、完整的 DDL 与元数据查看接口以及可选的RESTRICT ON操作级限制。读者阅读本文后将掌握在 TiDB 中为敏感列PAN、PII、日期、薪资等创建、修改、停用、删除脱敏策略的完整 SQL 操作理解 AT RESULT 求值语义、mysql.tidb_masking_policy系统表模型、InfoSchema 延迟加载与快照兼容契约等底层设计并能基于仓库源码pkg/ddl/masking_policy.go、pkg/infoschema/infoschema.go 等深入理解实现机制。背景为什么需要服务端列级脱敏受监管行业如需遵守支付卡行业数据安全标准 PCI DSS的企业必须严格管控谁能查看列的原始值——包括主账号号PAN、个人身份信息PII、日期属性等。在设计文档设计文档中明确指出TiDB 此前缺少原生的服务端列脱敏语义只能依赖应用侧手动编写 SQL 函数来脱敏这种方式难以在全链路保持一致地强制执行。本提案通过由 TiDB 原生强制的脱敏策略来弥合这一缺口。从当前仓库源码看该功能已完成从设计到实现的主体落地pkg/ddl/masking_policy.go821 行实现了onCreateMaskingPolicy等 DDL 执行路径pkg/infoschema/infoschema.go定义了maskingPolicyTableColumnMap、maskingPoliciesLoaded等字段支撑 InfoSchema 侧的策略加载pkg/parser/parser.y与pkg/parser/ast/ddl.go承载语法与 AST 节点。总体设计目标在 TiDB 中提供服务端列级数据脱敏支持基于会话身份current_user()、current_role()的条件脱敏逻辑支持全量/部分/置空/日期脱敏模式提供 SQL DDL 与 SHOW 接口覆盖策略完整生命周期管理提供可选的RESTRICT ON操作限制脱敏元数据持久化到系统表对运维与审计可见。非目标对非列数据日志、外部备份等脱敏对虚拟/生成列脱敏与 Oracle 语法/API 风格完全对齐全局解耦的策略绑定模型因复杂度和出错风险未采纳管理 BR/TiCDC 管线的跨组件用户/角色同步策略。策略模型实现将脱敏策略视为表拥有的 schema 对象而非游离的全局对象。这与工程师对类似索引的表附属物的思考方式一致先建表再附加策略让表的生命周期驱动策略生命周期。每条策略记录携带逻辑身份policy_name目标绑定db/table/column以及table_id/column_id脱敏表达式可选的RESTRICT ON操作集合运行时状态ENABLED/DISABLED。关键设计约束脱敏策略作用域为单表而非全局policy_name唯一性在单表内强制table_id policy_name一个列至多绑定一个策略table_id column_id。采用表级作用域避免了全局名称冲突并能自然映射到 DDL 所有权与清理逻辑。求值语义AT RESULT 重写脱敏实现为AT RESULT 重写表数据与谓词语义保持不变脱敏仅在产出查询结果时应用。从执行角度看planner/executor 在JOIN、WHERE、GROUP BY、HAVING、ORDER BY、集合操作中仍使用原始值返回的投影值由策略表达式基于运行时身份current_user()/current_role()重写。该选择最小化了优化器/存储层的回归风险兼容性风险低于谓词期重写。代价是除非通过RESTRICT ON限制原始值仍可能参与读写转换。架构与生命周期设计设计文档按代码归属拆分了实现工作而非按 SQL 语句清单拆分。DDL 写入路径从CREATE MASKING POLICY ...或ALTER TABLE ... ADD/MODIFY/... MASKING POLICY解析 DDL解析目标表/列并做目标与表达式校验将策略元数据持久化到mysql.tidb_masking_policy单一事实来源 SSOT发出 schema 变更ActionCreateMaskingPolicy/ActionAlterMaskingPolicy/ActionDropMaskingPolicy使InfoSchema中的策略缓存失效策略行在下次访问时惰性重载。源码佐证pkg/ddl/masking_policy.go的onCreateMaskingPolicy在写入前调用validateMaskingPolicyTarget校验目标再通过getMaskingPoliciesByTableIDFromSysTable检查表内已有策略以执行一个列一个策略、表内策略名唯一的约束meta.ErrMaskingPolicyExists。查询读取路径使用当前语句的InfoSchema或适用时的 stale/snapshotInfoSchema构建计划按(table_id, column_id)解析脱敏绑定用脱敏表达式重写结果投影AT RESULT 行为对于受限操作INSERT ... SELECT、UPDATE ... (SELECT ...)、DELETE ... (SELECT ...)、CTAS在执行前对脱敏源列做限制检查。元数据维护路径RENAME TABLE/RENAME COLUMN更新策略元数据的绑定名称同时保持基于 ID 的绑定连续性源码中replacePolicy逻辑即同步重命名后的名称/IDDROP COLUMN/DROP TABLE在mysql.tidb_masking_policy中同步清理策略对被脱敏列的类型/长度/精度变更设有防护guardrail防止策略与列发散。组件职责与代码归属组件职责源码位置设计文档标注Parser/AST策略 DDL 与RESTRICT ON的 SQL 语法与 AST 节点pkg/parser/parser.y、pkg/parser/ast/ddl.goDDL目标校验、元数据写入、生命周期同步、schema 动作pkg/ddl/executor.go、pkg/ddl/masking_policy.go、pkg/ddl/table.go、pkg/ddl/modify_column.goInfoSchema脱敏元数据的延迟加载与缓存失效pkg/infoschema/masking_policy.go、pkg/infoschema/masking_policy_loader.go、pkg/infoschema/builder.goPlanner/ExecutorAT RESULT 表达式重写与受限操作执行pkg/planner/core/point_get_plan.go、pkg/planner/core/masking_policy_restrict.go、pkg/executor/show.goBR 集成设计目标restore 时重放策略 DDL 语义而非对mysql.tidb_masking_policy做原始行重放—从当前仓库实际结构看InfoSchema 侧的策略缓存实现集中在 pkg/infoschema/infoschema.gomaskingPolicyTableColumnMap、maskingPoliciesLoaded、maskingPoliciesLoadCh、maskingPolicyMutex等字段与 pkg/infoschema/builder.go构建时克隆/初始化策略映射、InitWithDBInfos接收maskingPolicies []*model.MaskingPolicyInfo设计文档中规划的masking_policy.go、masking_policy_loader.go文件位置可作为后续实现的对照参考。BR 恢复兼容性设计脱敏策略元数据以mysql.tidb_masking_policy作为 SSOT但恢复不能盲目重放源表行原因在于BR 先恢复 schema 再恢复数据恢复后表/列 ID 可能与源集群不同直接重放源mysql.tidb_masking_policy行会把策略绑定到错误的目标 ID。正确设计为将源策略行作为逻辑策略定义读取从这些定义重建规范的CREATE MASKING POLICY ...语义在恢复 schema 阶段对恢复后的表执行重建的脱敏策略 DDL让目标集群生成正确的目标table_id/column_id绑定不对mysql.tidb_masking_policy直接重放源物理行。SQL 接口参考创建 / 添加策略CREATE [OR REPLACE] MASKING POLICY [IF NOT EXISTS] policy_name ON table_name (column_name) AS masking_expression [RESTRICT ON operation_list] [ENABLE | DISABLE];ALTER TABLE table_name ADD MASKING POLICY policy_name ON (column_name) AS masking_expression [RESTRICT ON operation_list] [ENABLE | DISABLE];规则OR REPLACE与IF NOT EXISTS互斥不支持临时表、系统表与视图一个列至多一个策略table_id column_id唯一策略名唯一性为表级作用域table_id policy_nameCREATE MASKING POLICY与ALTER TABLE ... ADD MASKING POLICY是等价的创建入口同一内部对象模型与运行时行为唯一用户可见的语法差异是OR REPLACE/IF NOT EXISTS仅在CREATE MASKING POLICY上可用。修改策略ALTER TABLE table_name ENABLE MASKING POLICY policy_name; ALTER TABLE table_name DISABLE MASKING POLICY policy_name; ALTER TABLE table_name DROP MASKING POLICY policy_name; ALTER TABLE table_name MODIFY MASKING POLICY policy_name SET EXPRESSION new_sql_expression; ALTER TABLE table_name MODIFY MASKING POLICY policy_name SET RESTRICT ON operation_list;元数据观察SHOW CREATE TABLE table_name; SHOW MASKING POLICIES FOR table_name; SHOW MASKING POLICIES FOR table_name WHERE column_name column_name;观察边界SHOW CREATE TABLE刻意保持轻量只告知运维人员某列存在策略及其是否启用不序列化完整策略定义完整策略检视交由SHOW MASKING POLICIES——这是面向表达式/restrict 元数据的详细运维接口。这种拆分让SHOW CREATE TABLE保持稳定可读同时为工具链提供专用的策略检视 API对应源码 pkg/executor/show.go。RESTRICT ON语义operation_list取值INSERT_INTO_SELECTUPDATE_SELECTDELETE_SELECTCTASNONE默认示例RESTRICT ON (INSERT_INTO_SELECT, DELETE_SELECT)执行时受限操作会针对脱敏源列做校验。该检查是语义性的TiDB 评估当前会话在绑定策略表达式下是否被有效允许读取未脱敏的源数据。若不允许语句将以脱敏访问拒绝错误被拒绝。表达式语义策略使用 SQL 表达式典型为CASE WHEN支持基于以下身份检查current_user()current_role()支持的比较符IN、NOT IN、、!。推荐默认拒绝default-deny行为不匹配放行条件的用户/角色应获得脱敏结果。典型的放行-否则脱敏模式形如AS CASE WHEN current_role() IN (AUDITOR_ROLE) THEN column_name ELSE MASK_PARTIAL(column_name, 6, 4, *) END内置脱敏函数函数说明支持类型示例MASK_PARTIAL(col, preserve_left, preserve_right, mask_char)部分脱敏字符串保留两端preserve_left为保留的前导字符数preserve_right为保留的尾部字符数mask_char为用于脱敏的单字符如*、XVARCHAR、CHAR、TEXTMASK_PARTIAL(credit_card, 6, 4, *)保留前 6 后 4 位MASK_FULL(col)完全脱敏整列值对 datetime 类型返回1970-01-01date或1970-01-01 00:00:00datetime字符串、datetime、数值类型MASK_FULL(ssn)对 9 位 SSN 返回XXXXXXXXXMASK_NULL(col)对列值返回 NULL任意支持类型MASK_NULL(salary)恒返回 NULLMASK_DATE(col, date_literal)将日期值替换为固定日期字面量date_literal为YYYY-MM-DD格式的固定日期如1970-01-01date/time 列MASK_DATE(birth_date, 1970-01-01)对任意日期值返回1970-01-01支持的列类型主要支持范围字符串类VARCHAR、CHAR、TEXT家族、BLOB家族时间类DATE、TIME、DATETIME、TIMESTAMP、YEAR数值类整型 / 浮点 / decimal 类型。对于LONGTEXT与BLOB要求的最低行为是全量脱敏full masking或置空脱敏null masking。DDL 防护与级联删除DDL 防护对被脱敏列做类型/长度/精度修改会被阻止防止策略与列定义发散造成安全缺口级联删除删除被脱敏列或其所属表会同步清除关联的脱敏策略元数据。系统表设计脱敏元数据存储于mysql.tidb_masking_policy参考 schemaCREATE TABLE mysql.tidb_masking_policy ( policy_id bigint(64) NOT NULL AUTO_INCREMENT, policy_name varchar(64) NOT NULL, db_name varchar(64) NOT NULL, table_name varchar(64) NOT NULL, table_id bigint(64) NOT NULL, column_name varchar(64) NOT NULL, column_id bigint(64) NOT NULL, expression text NOT NULL, status varchar(16) NOT NULL, masking_type varchar(32) NOT NULL, restrict_on varchar(256) NOT NULL DEFAULT NONE, created_at datetime(6) NOT NULL, updated_at datetime(6) NOT NULL, created_by varchar(288) NOT NULL DEFAULT , PRIMARY KEY(policy_id), UNIQUE KEY uk_table_policy(table_id, policy_name), UNIQUE KEY uk_table_column(table_id, column_id) );两条唯一键正是策略模型约束表内策略名唯一、一列一策略的物理表达。单一事实来源SSOT设计决策存储设计刻意将mysql.tidb_masking_policy作为唯一运行时事实来源策略元数据既不复制进TableInfo也不维护第二个元数据通道。理由避免双写顺序与恢复复杂性导致的 buggy 实现避免元数据副本 A 与系统表副本 B发散处理容忍用户直接误操作mysql.tidb_masking_policy将策略演进逻辑集中在一条路径。这意味着策略在逻辑上归表所有但物理上存储在隔离的系统表元数据中更接近权限元数据的存储风格。通过table_id/column_id仍保留稳定绑定因此重命名操作不会破坏策略关联策略相关 DDL 后内存策略缓存失效并按需重建。InfoSchema 加载模型引导/schema 构建期间存在依赖环风险读取mysql.tidb_masking_policy需要 SQL 执行SQL 执行又需要可用的InfoSchema而在初始InfoSchema构建时急切物化策略会重新进入该依赖。实现通过延迟策略加载解决先构建基础InfoSchema基础InfoSchema可用后通过受限 SQL 加载策略行物化以(table_id, column_id)为键的内存映射。源码佐证pkg/infoschema/infoschema.go中的maskingPoliciesLoaded标志、maskingPoliciesLoadCh加载进行中的通道与maskingPolicyMutex锁正是这一延迟加载与并发控制模型的落地pkg/infoschema/builder.go的InitWithDBInfos接收maskingPolicies []*model.MaskingPolicyInfo完成初始化。这保证了初始化路径无环同时保持运行时策略正确性。快照 / 陈旧读兼容契约由于策略元数据外部化不嵌入表元数据必须显式定义快照行为任何在读取时间戳T执行的语句策略状态解析与表 schema 解析必须观察同一时间线。实践中旧 schema 最新策略或反之被视为无效行为。工程与测试的预期行为T时无可见策略 ⇒T时无脱敏T时有启用的策略 ⇒ 按T时的策略定义求值脱敏。该契约是tidb_snapshot、AS OF TIMESTAMP、陈旧事务模式、tidb_read_staleness的兼容基线。授权模型脱敏策略管理使用全局作用域ON *.*的动态权限GRANT CREATE MASKING POLICY ON *.* TO security_admin%; GRANT ALTER MASKING POLICY ON *.* TO security_admin%; GRANT DROP MASKING POLICY ON *.* TO security_admin%;权限描述CREATE MASKING POLICY允许创建新策略对象并通过ALTER TABLE ... ADD MASKING POLICY或CREATE MASKING POLICY绑定到列。ALTER MASKING POLICY允许修改既有脱敏策略定义包括SET EXPRESSION、SET RESTRICT ON与ENABLE/DISABLE。DROP MASKING POLICY允许从列/表上永久移除脱敏策略。ALTER MASKING POLICY包含SET EXPRESSION、SET RESTRICT ON、ENABLE/DISABLE。运行时数据暴露仍由策略表达式逻辑current_user()/current_role()条件控制而非 DDL 权限本身。运维工作流示例安全管理员被授予脱敏策略管理权限管理员创建用于未脱敏访问的业务角色例如AUDITOR_ROLE管理员定义带角色/用户放行名单逻辑的策略管理员将该角色授予目标用户激活了相应角色的授权会话读到原始值其他会话读到脱敏值。设计权衡设计优先保证可预测的 SQL 行为与更低的发布风险使用显式 SQL 策略对象与基于表达式的规则易于审计与推理保持存储与谓词语义不变AT RESULT减少执行路径回归使用带稳定内部 ID 的按列绑定可在重命名操作后存活增加可选的RESTRICT ON控制满足更严格的合规需求而不默认强加 Oracle 式限制。需要维护者明确的权衡外部化策略存储改善元数据一致性处理但要求仔细的缓存/快照协调AT RESULT 脱敏保持 planner/存储行为稳定但数据流限制必须通过RESTRICT ON显式处理轻量SHOW CREATE TABLE提升可读性但详细工具链必须使用SHOW MASKING POLICIES。使用示例-- 示例 1MASK_PARTIAL - 保留电话号码前 3 与后 3 位 CREATE TABLE contacts ( id INT PRIMARY KEY, name VARCHAR(100), phone VARCHAR(20) ); CREATE MASKING POLICY p_mask_phone ON contacts(phone) AS MASK_PARTIAL(phone, 3, 3, *) ENABLE; INSERT INTO contacts VALUES (1, Alice, 1234567890); -- 查询返回123****890中间脱敏 SELECT phone FROM contacts WHERE id 1; -- 示例 2MASK_FULL - 完全脱敏 SSN CREATE MASKING POLICY p_mask_ssn ON employees(ssn) AS MASK_FULL(ssn) ENABLE; -- 查询返回脱敏后的 SSN 值 SELECT ssn FROM employees WHERE id 1; -- 示例 3MASK_DATE - 归一化出生日期 CREATE MASKING POLICY p_mask_birthdate ON users(birth_date) AS MASK_DATE(birth_date, 1970-01-01) ENABLE; -- 查询返回任意出生日期均返回 1970-01-01 SELECT birth_date FROM users WHERE id 1; -- 示例 4MASK_NULL - 隐藏薪资信息 CREATE MASKING POLICY p_mask_salary ON employees(salary) AS MASK_NULL(salary) ENABLE; -- 查询返回所有薪资值均为 NULL SELECT salary FROM employees WHERE id 1;兼容性语法与行为为 TiDB 专有非 MySQL 兼容特性对齐BR/TiCDC 可将脱敏相关 DDL 与元数据语义携带到下游集群运行时脱敏决策依赖身份求值current_user()/current_role()下游集群中缺失或分叉的用户/角色状态可能改变实际脱敏行为BR 恢复应通过针对恢复目标表重放CREATE MASKING POLICY语义来重建脱敏策略而非直接重放mysql.tidb_masking_policy源行跨集群恢复的表/列 ID 可能不同转储/校验工具若由非豁免用户执行可能看到脱敏值。注意BR/TiCDC 中的用户/角色同步行为不在本脱敏策略设计范围内。本设计仅定义脱敏策略语义与元数据行为。因此下游脱敏验证必须将用户/角色对齐视为硬性前置条件。若下游用户/角色状态未经显式核对与验证脱敏验证结果必须视为无效。当前行为可能因组件、版本与 flags/filters 而异例如 BR--with-sys-table与表过滤器、TiCDC 系统 schema 过滤运维人员必须在每个部署中核实下游实际用户/角色状态。实施范围高级实施范围Parser/AST 支持脱敏策略 DDL 与RESTRICT ON语法在mysql.tidb_masking_policy中持久化元数据Planner/executor 集成 AT RESULT 脱敏表达式求值策略管理操作的权限检查SHOW/检视接口SHOW CREATE TABLE、SHOW MASKING POLICIES ...被脱敏列的 DDL 防护与级联删除钩子脱敏绑定变化时的计划缓存失效。非功能预期因逐行表达式与身份检查产生轻微读延迟开销TiKV/TiFlash 的推下机会应在批量负载下做基准测试。待解决问题受限操作中脱敏访问拒绝的最终错误码选择应与既有 TiDB/MySQL 错误码分配策略对齐精确的优化器推下与计划缓存失效策略应通过实现基准测试验证外键相关推断风险需要在用户文档中给出清晰运维指引。深入阅读设计文档原文docs/design/2026-02-27-column-level-masking.mdDDL 执行路径pkg/ddl/masking_policy.go、pkg/ddl/masking_policy_test.go、pkg/ddl/masking_policy_internal_test.goInfoSchema 加载与缓存pkg/infoschema/infoschema.go、pkg/infoschema/builder.goParser/ASTpkg/parser/parser.y、pkg/parser/ast/ddl.go相关背景TiDB 动态权限设计可参见 docs/design/2021-03-09-dynamic-privileges.md【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考