数据分类分级是所有合规工作的地基,也是等保 2.0 三级测评里最容易被扣分的环节:制度文件写得漂亮,一问「身份证号存在哪几张表、定了什么级别、谁有权限看」却答不上来。本文给出一套可执行的落地流程,用 SQL 拉元数据、用脚本做内容采样识别敏感字段、用统一的级别映射表驱动管控措施,最终产出可交付的资产台账与分级清单。
适用场景
- 准备等保 2.0 三级测评,需要提交数据资产清单与分级说明。
- 业务系统表数量在几十到几百张之间,靠人工翻表结构梳理敏感字段不现实。
- 需要回答「哪些字段属于个人信息、哪些属于重要数据」以及对应的加密、脱敏、审计要求。
- 发生过数据外泄事件后,需要明确最小必要授权范围与访问审批链路。
前置条件
- MySQL 5.7+/8.0 或 PostgreSQL,具备一个只读账号,仅授权
information_schema与目标库的SELECT。 - Python 3.9+,依赖
pymysql;若用 PostgreSQL 换psycopg2即可,逻辑不变。 - 已梳理业务流程,能确认哪些表属于核心交易、哪些属于对外展示。
- 明确采用的级别定义。本文按四级划分:L1 公开、L2 内部、L3 敏感、L4 核心,可与企业现有制度对齐后改名。
原理说明
分类与分级是两件事。分类回答「这是什么数据」,按业务属性划分,如用户身份、交易订单、设备日志、内容数据;分级回答「泄露后影响多大」,按影响程度划分。同一条数据既有一个分类,也有一个级别,二者组成二维坐标,后续的管控措施由这个坐标决定。
敏感字段识别不能只看字段名。实践中采用三条线索交叉验证:一是元数据线索,字段名或注释命中关键词(id_card、phone、bank);二是内容线索,对字段抽样取值跑正则,命中率超过阈值即判定;三是业务线索,通过访谈确认该字段的实际语义。单用元数据会漏掉 c_no 这种命名,单用内容则会把订单号误判成银行卡号,所以必须交叉。
级别一旦确定,管控措施就要固定下来,避免每张表各自为政。核心原则是:级别决定加密与脱敏强度,分类决定审计粒度。L3/L4 字段在传输与存储上必须加密或脱敏,L4 还要额外做访问审批与操作留痕。
操作步骤
步骤一:建立资产台账表
在独立的合规库里建三张表,分别存资产、字段、分级结果。台账必须可追溯「谁在什么时候定的级」,否则复评时无从下手。
CREATE TABLE dc_asset (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
db_name VARCHAR(64) NOT NULL,
table_name VARCHAR(64) NOT NULL,
table_comment VARCHAR(255),
biz_domain VARCHAR(64) COMMENT '业务分类',
owner VARCHAR(64) COMMENT '数据责任人',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_tbl (db_name, table_name)
);
CREATE TABLE dc_column (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
asset_id BIGINT NOT NULL,
col_name VARCHAR(64) NOT NULL,
col_type VARCHAR(64),
col_comment VARCHAR(255),
sample_hit DECIMAL(5,2) DEFAULT 0 COMMENT '内容命中率%',
hit_rule VARCHAR(64) COMMENT '命中的识别规则',
level TINYINT COMMENT '1公开 2内部 3敏感 4核心',
reviewed TINYINT DEFAULT 0,
KEY idx_asset (asset_id)
);
步骤二:拉取元数据与字段清单
SELECT c.table_schema, c.table_name, t.table_comment,
c.column_name, c.column_type, c.column_comment
FROM information_schema.columns c
JOIN information_schema.tables t
ON t.table_schema = c.table_schema AND t.table_name = c.table_name
WHERE c.table_schema NOT IN ('mysql','sys','information_schema','performance_schema')
AND t.table_type = 'BASE TABLE'
ORDER BY c.table_schema, c.table_name, c.ordinal_position;
这条语句能一次拿到全库表与字段结构,输出到 CSV 后即可作为台账的初始骨架。字段注释为空是常态,不要指望它,识别主要靠下一步的内容采样。
步骤三:内容采样识别敏感字段
脚本对每个字段抽样 200 行(LIMIT 加 ORDER BY RAND() 会全表扫,大表改用主键区间随机),跑规则集计算命中率,命中率 ≥ 60% 判定为该类型。
#!/usr/bin/env python3
import re, pymysql
RULES = {
"id_card": (re.compile(r"^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$"), 4),
"mobile": (re.compile(r"^1[3-9]\d{9}$"), 3),
"bank_card": (re.compile(r"^\d{16,19}$"), 4),
"email": (re.compile(r"^[\w.+-]+@[\w-]+\.[\w.]+$"), 2),
"address": (re.compile(r"[\u4e00-\u9fa5]{2,}(省|市|区|县|路|街|号)"), 2),
}
def luhn(num):
"""银行卡号校验,用于排除订单号、流水号等纯数字串的误报"""
digits = [int(d) for d in num][::-1]
total = 0
for i, d in enumerate(digits):
if i % 2 == 1:
d *= 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
def classify(values):
best, best_ratio = None, 0.0
for name, (rx, level) in RULES.items():
hit = sum(1 for v in values if rx.match(v))
ratio = hit / len(values) if values else 0.0
if name == "bank_card":
hit = sum(1 for v in values if rx.match(v) and luhn(v))
ratio = hit / len(values) if values else 0.0
if ratio > best_ratio:
best, best_ratio = (name, level), ratio
return (best, best_ratio) if best_ratio >= 0.6 else (None, best_ratio)
conn = pymysql.connect(host="127.0.0.1", user="dc_ro", password="***",
charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor)
cur = conn.cursor()
cur.execute("""SELECT table_schema db, table_name tb, column_name col
FROM information_schema.columns
WHERE table_schema = 'shop'
ORDER BY table_name, ordinal_position""")
cols = cur.fetchall()
for c in cols:
sql = "SELECT `%s` AS v FROM `%s`.`%s` WHERE `%s` IS NOT NULL LIMIT 200" % (
c["col"], c["db"], c["tb"], c["col"])
try:
cur.execute(sql)
values = [str(r["v"]).strip() for r in cur.fetchall() if str(r["v"]).strip()]
except Exception as e:
print("[SKIP]", c["tb"], c["col"], e)
continue
if len(values) < 5:
continue
(rule, level), ratio = classify(values)
if rule:
print("[HIT] %s.%s -> %s L%d %.1f%%" % (c["tb"], c["col"], rule, level, ratio * 100))
两个细节决定结果可信度:正则必须用 ^...$ 全串匹配,否则一段文本里含手机号也会命中,误报率飙升;银行卡号必须过 Luhn 校验,否则 16 位订单号会整片误判为 L4 核心数据,后续管控成本凭空翻倍。
步骤四:定级与人工复核
脚本输出的是建议值,必须人工逐条确认 reviewed=1。定级参考下表,同一字段在不同业务场景下级别可以不同,例如客服可见的手机号是 L3,而出现在对外导出报表中则应视为 L4。
| 级别 | 判定标准 | 典型数据 | 管控要求 |
|---|---|---|---|
| L1 公开 | 可公开披露,泄露无影响 | 商品标题、门店地址 | 无特殊要求 |
| L2 内部 | 仅内部使用,泄露影响有限 | 邮箱、部门、内部编号 | 登录鉴权、禁止外发 |
| L3 敏感 | 涉及个人信息,泄露造成权益损害 | 手机号、住址、订单明细 | 脱敏展示、访问审计、传输加密 |
| L4 核心 | 泄露造成严重后果或监管处罚 | 身份证号、银行卡、生物特征 | 存储加密、动态脱敏、双人审批 |
步骤五:按级别落地管控措施
先做权限收敛:给应用账号只授予必要字段的查询权,L4 字段单独建视图控制可见性。
-- L4 字段不外露,对外只提供脱敏视图
CREATE VIEW v_user_safe AS
SELECT id, user_name,
CONCAT(LEFT(id_card, 6), '********', RIGHT(id_card, 4)) AS id_card_mask,
CONCAT(LEFT(mobile, 3), '****', RIGHT(mobile, 4)) AS mobile_mask
FROM t_user;
GRANT SELECT ON shop.v_user_safe TO 'app_read'@'10.0.1.%';
REVOKE SELECT ON shop.t_user FROM 'app_read'@'10.0.1.%';
再做审计留痕:MySQL 开启通用日志会带来明显性能开销,建议只对含 L4 字段的库开启审计插件,或使用 audit_log 按用户过滤。PostgreSQL 侧可用 pgaudit 针对特定表设置 pgaudit.log='read,write'。
最后补导出管控:所有含 L3/L4 字段的导出必须走审批,导出文件加水印与有效期,禁止直接落到个人终端。这一条在测评中常被单独询问,属于「管理制度与技术手段对应」的必查项。
配置验证
- 识别准确率抽查:随机抽 20 个被判定为 L3/L4 的字段,人工核对真实数据,命中率应不低于 90%;同时抽 10 个未命中字段,确认没有漏判。
- 误报可控:检查
bank_card规则命中的字段中是否混入订单号、运单号,Luhn 校验后应基本清零。 - 权限确实收敛:用应用账号执行
SELECT id_card FROM t_user LIMIT 1;应报权限错误,而SELECT * FROM v_user_safe LIMIT 1;能返回脱敏结果。 - 审计能查到:以普通业务账号查询一次敏感表,然后在审计日志中
grep该账号与表名,应能找到对应记录(含时间、来源 IP、语句)。 - 清单完整:资产台账中
reviewed=0的记录数应为 0,否则说明还有字段未定级,测评时会被直接判定为「未完成梳理」。
常见问题
FAQ 1:一定要分四级吗,三级行不行?
级别数量没有强制统一,关键是与采用的制度对齐并保持内部一致。等保 2.0 与《数据安全法》体系下常用三到五级,中小企业从三级起步完全可行:公开、内部、敏感三级已能覆盖绝大多数管控差异。选四级的理由是「敏感」与「核心」在管控成本上差距明显(是否加密存储、是否双人审批),混在一级会导致 L4 字段被按普通敏感数据处理,属于典型的高风险妥协。
FAQ 2:字段名起得乱七八糟(c_no、f2、col3),识别还有效吗?
有效,但必须完全依赖内容采样而非字段名。本文脚本的判定依据是抽样值的正则命中率,与字段名无关,条件只是该字段确实存了可识别的结构化数据。真正无解的是「混合文本」字段,比如备注里偶尔出现身份证号,这类字段正则命中率达不到 60% 阈值,需要单独做全文扫描:对备注类字段抽样后跑非锚定正则统计出现次数,命中即标记为「可能含敏感信息」,按 L3 处理并要求业务侧整改。
FAQ 3:几百张表、上千万行数据,采样识别会不会把库拖垮?
会,所以必须限流。三条措施:一是只读从库执行,绝不在主库跑;二是控制采样量,每字段 200 行足够计算命中率,不需要全表;三是避开高峰并加 SELECT ... LIMIT 强制截断,大表用主键区间随机(WHERE id BETWEEN ? AND ?)替代 ORDER BY RAND(),后者会触发全表排序。整个扫描建议放在夜间窗口,单次运行控制在 30 分钟内,超时即中断并在次日续跑。
总结
数据分类分级的产出不是一份文档,而是三样可以持续更新的东西:一张覆盖全部字段的资产台账、一套能自动跑的识别脚本、一张级别与管控措施的对应矩阵。落地节奏建议先挑一个核心业务库做试点,把识别准确率与误报率调到位,再横向铺开到其他库。定级完成后必须建立复评机制——新增表上线前先定级、业务变更时同步复核、每年做一次全量回顾。否则台账三个月就与现实脱节,测评时反而成为「制度与实际不符」的证据。