ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YashanDB数据质量提升:从建表约束到监控的5种实用方法

YashanDB数据质量提升:从建表约束到监控的5种实用方法 真正把YashanDB的数据质量搞上去靠的不是事后补救而是从设计、写入、清洗、监控全链路一起使劲。我接触YashanDB也有段时间了刚开始踩了不少坑——表结构随便建、应用层不设防、重复数据堆成山等报表跑出来才发现数字对不上再回头一根根扒数据那叫一个痛苦。后来我总结出5种最有效的方法从源头到运维一层层堵住脏数据实测下来数据准确率明显提升。这篇文章就把这些方法完整拆开讲适合正在用YashanDB做业务系统、数仓项目的开发者和DBA参考新手也能照着操作。1. 从表结构设计入手堵住脏数据入口数据质量的第一道闸门不是代码是表结构。很多数据问题字段为空、格式混乱、超出范围如果能在建表时用约束卡死后面根本不会发生。YashanDB支持完整的关系模型约束包括主键、唯一约束、非空、CHECK和默认值但实际项目里能把这些用全的表少之又少。1.1 非空、默认值与CHECK约束的精细化设计先说最简单的。比如一张用户表注册时间字段如果允许为空后面统计“本月新增用户”时就会出现漏数。正确的做法是业务上必然存在的字段一律加NOT NULL并给一个合理的默认值。像创建时间直接默认当前时间戳状态字段默认一个初始值。这样就算应用层漏传数据库也能兜底。CHECK约束是很多人忽略的利器。比如年龄字段正常情况下是0到120之间加一个CHECKage BETWEEN 0 AND 120就能挡住那些“年龄9999”的妖怪数据。再比如性别字段虽然不建议用CHECK代替枚举表但至少可以限定范围。还有一个经典场景订单金额必须大于0如果业务上有优惠券导致0元订单也得记录那可以规定金额大于等于0但必须配合其他字段校验。这类约束看起来不起眼却是在数据库层面挡住了大量无效数据。CREATE TABLE user_profile ( user_id NUMBER PRIMARY KEY, user_name VARCHAR2(64) NOT NULL, age NUMBER(3) CHECK (age BETWEEN 0 AND 120), gender CHAR(1) DEFAULT U CHECK (gender IN (M, F, U)), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL );提示YashanDB对CHECK约束的处理与主流数据库一致如果后续需要调整约束用ALTER TABLE ENABLE/DISABLE CONSTRAINT即可不需要重建表。1.2 主键、唯一约束与业务键的权衡主键的作用不只是去重更重要的是确保每一行记录都有唯一身份。但实际业务中自然主键比如身份证号往往不稳定或会变更这时候用自增序列做主键更稳妥再另加唯一约束去保证业务键唯一。比如用户表user_id是代理主键而user_name或者手机号应该加唯一约束。这样既保证了每一行可被稳定引用又避免了业务上重复注册。这里要特别留意一个细节唯一约束对NULL值不生效也就是说如果手机号允许为空那么100个空手机号的记录不会被唯一约束拦截。所以如果业务要求“一个手机号只能注册一次”但注册时手机号又可能为空那就不能只靠唯一约束得在应用层或触发器里做条件判断。YashanDB支持函数索引可以对非空字段建立条件唯一索引这个技巧很实用CREATE UNIQUE INDEX idx_user_mobile_unique ON user_profile (CASE WHEN mobile IS NOT NULL THEN mobile END);在Oracle和YashanDB里这种基于CASE表达式的唯一索引可以做到“只对非空值去重”既允许空值存在又保证非空值不重复。这是我后来用得最多的手段之一。2. 应用层与数据库层双重校验让坏数据进不来约束是最后一道防线但如果我们能早早在应用层发现问题就能减少数据库无谓的开销也方便给出友好的错误提示。理想状态是应用层先粗筛数据库层再兜底两层配合坏数据几乎没有机会落库。2.1 应用层参数校验的常见漏网点很多后端团队只校验必填字段却忽略了格式、长度、取值范围。举个例子注册接口只校验了用户名非空没校验用户名长度结果一个用户把几千字的文本塞进去数据库字段定义VARCHAR2(20)直接报ORA-12899类似的过长错误或者被截断导致数据失去意义。所以在应用层至少要校验长度、格式邮箱、手机号、日期、枚举值是否合法、数值范围是否合理。如果是Java后端可以用Bean Validation注解如果是Python可以用Pydantic或者手写校验函数。原则是“疑罪从有”拿不准的都校验一遍。另一个容易被忽略的是批量导入场景。很多系统都提供Excel导入功能如果导入前不校验坏数据就会成批进入YashanDB。我的经验是先解析文件到临时表做完整校验再通过MERGE或存储过程搬入正式表校验不通过的行要生成错误清单反馈给用户而不是直接丢弃。这样既保证了正式表的数据质量又能让用户根据错误清单修改后重新导入。2.2 触发器与存储过程实现复杂校验的时机有些校验逻辑非常复杂涉及跨表查询比如“用户当天只能提交一次申请”。这种场景靠应用层容易漏靠简单约束又办不到用触发器比较合适。YashanDB支持BEFORE INSERT/UPDATE触发器可以在写入前拦截。但要注意触发器是事务里执行会加大锁的持有时间高并发下性能会受影响。因此触发器只应处理那些应用层无法保证的、必须原子校验的逻辑不要把所有业务校验都塞进去。如果项目里还有多个应用Web端、后台任务、报表工具都在写同一张表各自应用层校验逻辑不统一的话数据库层的触发器反而是最可靠的统一收口方案。我通常会在核心业务表上建一个“校验触发器 日志表”记录拦截下来的非法数据方便事后分析。日志表本身只增不改不影响主业务流程。CREATE OR REPLACE TRIGGER trg_check_daily_apply BEFORE INSERT ON apply_record FOR EACH ROW DECLARE v_cnt NUMBER; BEGIN SELECT COUNT(*) INTO v_cnt FROM apply_record WHERE user_id :NEW.user_id AND TRUNC(apply_date) TRUNC(:NEW.apply_date); IF v_cnt 0 THEN RAISE_APPLICATION_ERROR(-20001, 同一用户当天只能申请一次); END IF; END;注意触发器里的RAISE_APPLICATION_ERROR会使整个写入事务回滚应用层必须捕获并展示友好提示避免给用户留下“系统崩溃”的印象。3. 数据清洗与标准化用SQL给数据“洗澡”哪怕是已经上了约束和校验的表历史数据里也难免有空格、全半角混淆、大小写不统一、日期格式错乱等问题。这些数据不洗报表做出来就是脏的。清洗的工具有很多但直接在YashanDB里用SQL处理是最直接、成本最低的。3.1 去除空格、统一大小写与全半角转换最常见的问题是字符串里的隐形脏字符。比如用户提交姓名时带了前后空格或者中间出现多个空格再比如输入法导致的全半角差异看起来是一样的字实际编码不同。处理方式很简单先用TRIM去掉首尾空格再用REPLACE把全角空格和全角标点替换成半角最后用正则表达式把多个连续空格合并成一个。UPDATE user_profile SET user_name REGEXP_REPLACE( TRIM( REPLACE( REPLACE(user_name, , ), -- 全角空格转半角 , , ) ), \s{2,}, ) WHERE user_name ! REGEXP_REPLACE( TRIM( REPLACE( REPLACE(user_name, , ), , , ) ), \s{2,}, );这段SQL的精髓在于WHERE条件里加了一个对比只更新真正需要洗的行避免触发无谓的日志写入和索引维护。数据量大时这种“先对比再更新”的思路能省下大量redo和undo空间。日期格式也是重灾区。有的系统存字符串‘2024/01/05’有的是‘2024-01-05’还有‘20240105’。清洗时需要统一转成标准日期。YashanDB支持TO_DATE和TO_CHAR可以先识别格式再转换也可以用CASE WHEN把不同格式逐一处理。碰到无法识别的值先记到异常表里不要直接丢否则审计时说不清。3.2 利用MERGE实现增量清洗与标准化大批量清洗时不建议直接UPDATE原表除非表特别小。比较稳妥的做法是先把需要清洗的数据抽到清洗临时表做好各种转换和校验再通过MERGE回写正式表。MERGE的好处是一次SQL既能更新已有行又能插入新行非常适合“清洗补全”场景。YashanDB对MERGE的支持很完善和Oracle语法一致。MERGE INTO user_profile t USING cleaned_data s ON (t.user_id s.user_id) WHEN MATCHED THEN UPDATE SET t.user_name s.user_name, t.mobile s.mobile WHEN NOT MATCHED THEN INSERT (user_id, user_name, mobile) VALUES (s.user_id, s.user_name, s.mobile);注意MERGE的USING子句里如果出现重复的user_id会导致“ORA-30926: unable to get a stable set of rows”之类的错误。所以在清洗临时表里一定要先对关联键去重。清洗过程的每步都建议留日志比如每个清洗规则命中了多少行、转换失败多少行便于复盘。3.3 数据标准化与字典表映射还有一种需要清洗的情况是“同义不同值”。比如性别字段有的系统存“男”有的存“M”有的存“1”统计的时候就乱套了。解决思路是建立标准字典表通过映射关系把老数据洗成标准码。字典表建议做成“源值 - 标准值 - 标准描述”的结构并记录来源系统方便以后溯源。CREATE TABLE dict_gender_mapping ( source_value VARCHAR2(20) PRIMARY KEY, standard_code CHAR(1) NOT NULL, standard_desc VARCHAR2(20) NOT NULL ); INSERT INTO dict_gender_mapping VALUES (男, M, 男性); INSERT INTO dict_gender_mapping VALUES (M, M, 男性); INSERT INTO dict_gender_mapping VALUES (1, M, 男性);清洗时把业务表和字典表做关联更新标准码。如果遇到字典表里没有的源值说明又出现了未预料的脏数据要记录下来补充映射。标准化是一个持续迭代的过程不可能一次性做完所以清洗脚本和字典表本身也要纳入版本管理。4. 去重与主数据治理消灭“同一人多条记录”数据质量另一个大问题是重复。重复数据造成统计虚高、客户被重复触达、对账不平。去重不能简单“DELETE掉多余的”要先搞清楚业务上怎么定义“重复”以及保留哪一条。4.1 基于业务键识别重复并保留有效记录拿客户表举例同一个客户可能因为导入来源不同被录入了两次姓名、手机号都相同只是其中一条更新了地址。去重的第一步是先定义“重复”的判定规则完全一致还是手机号一致就算判定规则不同去重方案完全不同。实际业务里往往需要用多个字段的组合甚至加上模糊匹配比如姓名相同且生日相同来判断。判定好重复后第二步是决定保留哪一条。我的建议是保留“信息最全”或“最后更新时间最近”的一条并把其他记录的关联业务订单、工单逐步迁移到保留下来的主记录上。直接删除会导致外键报表丢失数据所以正确做法是“合并”而不是“删除”。可以给每一条客户记录加一个master_id字段指向最终保留的主记录查询时统一用master_id聚合。YashanDB中可以用ROW_NUMBER()窗口函数来给重复组编号再批量标记UPDATE customer c SET master_id ( SELECT keep_id FROM ( SELECT cust_id AS keep_id, ROW_NUMBER() OVER ( PARTITION BY mobile ORDER BY last_update_time DESC, cust_id ) AS rn FROM customer ) k WHERE k.cust_id c.cust_id ) WHERE c.master_id IS NULL;这段SQL先按手机号分组每组按最后更新时间倒序排更新时间最新、id最小的那条rn1就把它作为该组的master_id。之后业务查询都以master_id为准重复数据不再产生负面影响。4.2 唯一索引配合合并流程防止重复再生光清理存量重复还不够必须防止新重复产生。最直接的办法就是给业务键加唯一索引。但业务场景往往复杂比如“同一手机号允许出现多次但只能有一个有效状态为‘正常’的”这就不能用普通唯一索引。YashanDB支持函数索引和部分唯一索引利用CASE表达式可以实现这种条件唯一。CREATE UNIQUE INDEX idx_customer_one_active ON customer (CASE WHEN status ACTIVE THEN mobile END);这个索引的逻辑是当状态为ACTIVE时对手机号去重而状态为INACTIVE的历史记录不做限制。这样在合并老数据时可以先保留一条ACTIVE其他置为INACTIVE之后系统层面就再也不会出现两条有效客户了。这个方法我在实际项目中解决了“一人多卡”类型的问题效果立竿见影。4.3 导入与同步场景下的幂等控制另一个重复来源是数据同步。比如从外部系统同步客户数据因为网络原因任务跑了两遍结果重复插入。解决方案是给同步表加一个“源系统ID源表主键”的组合唯一索引并采用INSERT ... ON CONFLICT或MERGE方式写入而不是简单的INSERT。YashanDB兼容OracleMERGE是最常用的幂等同步手段。如果同步工具不支持MERGE也可以在应用层先按条件查询再决定插入还是更新但会有并发窗口所以最稳的还是数据库层的唯一索引兜底。注意凡是涉及外部系统对接的接口一定要让外部系统提供唯一业务键否则数据质量无从谈起。5. 数据质量监控与定期体检防患于未然前四步解决的是“如何防止脏数据产生”和“如何清理存量脏数据”但数据质量不是一劳永逸的。业务变化、版本迭代、新系统接入都可能引入新的问题。因此必须建立一套持续监控机制。5.1 设计数据质量规则并定期跑批我建议把数据质量规则沉淀成一张规则表包含表名、字段名、规则类型非空、唯一、取值范围、正则、跨字段一致性、阈值、负责人。然后写一个定期任务比如每天凌晨遍历执行这些规则把跑批结果写入质量报告表。例如检查订单表里“订单金额为负数且状态不是已取消”的记录数INSERT INTO data_quality_report (check_date, rule_name, bad_count, details) SELECT CURRENT_DATE, order_amount_non_negative, COUNT(*), LISTAGG(order_id, ,) WITHIN GROUP (ORDER BY order_id) FROM orders WHERE amount 0 AND status NOT IN (CANCELED) HAVING COUNT(*) 0;如果bad_count超过阈值就触发告警发邮件或者企业微信机器人通知。这类任务用数据库的JOB调度即可YashanDB支持DBMS_SCHEDULER类似的调度能力不需要额外搭一套调度平台。重点是规则要不断迭代每发现一种新脏数据就补充一条新规则。5.2 审计日志与变更追踪数据质量出问题最后总要定位是哪个环节出的错。这时候审计日志就是救命稻草。建议对核心表开启审计或者通过触发器记录关键字段的变更前值、变更后值、操作人、操作时间。YashanDB本身提供审计功能可以配置对DDL和DML的审计但业务级的“谁改了订单金额”还需要业务表里记录操作日志。我自己的习惯是核心业务表都带一个last_updated_by字段每次更新必须由应用层传入当前用户。然后定期归档变更日志。遇到数据对不上时按时间倒序查日志很快就能定位到操作源头。没有审计日志出了问题只能干瞪眼。5.3 同步链路中的数据质量校验如果环境里用了数据库同步工具比如从业务库同步到数仓同步过程中也可能引入数据质量问题比如延迟导致先读旧数据、主键冲突、类型转换失败等。我的经验是同步任务里必须加上“行数和校验和对比”这一环。每次同步完成后对比源表和目标表的行数、关键字段的SUM值或MD5采样值不一致立刻告警。YashanDB在数仓场景中可以和这些同步工具配合但数据质量的最终责任还是在自己这边不能指望同步工具自动保证。SELECT COUNT(*), SUM(amount) FROM ordersdblink_src WHERE update_time last_sync_time; SELECT COUNT(*), SUM(amount) FROM orders WHERE update_time last_sync_time;两条语句的结果如果对不上说明同步链路出了问题需要查看同步任务日志。这里提醒一句做数据迁移或同步前一定要先做全量校验再切增量。6. 常见问题与排查技巧实录最后整理一下我在YashanDB数据质量实践中遇到的高频问题直接给解决方案。现象可能原因排查与解决唯一约束没拦住重复数据重复字段中有NULL值改用函数唯一索引只对非空值去重UPDATE清洗时卡死或慢没有先加WHERE条件全表更新先查询准备更新的行用小批提交MERGE报错“unable to get a stable set of rows”USING结果集中关联键重复对USING结果用ROW_NUMBER去重CHECK约束不生效字段在写入时被隐式类型转换检查插入数据的类型使用显式TO_NUMBER数据同步导致重复同步任务重复执行加源系统唯一键使用MERGE幂等写入报表数字偏大关联多表时产生笛卡尔积检查关联字段是否有唯一索引先聚合再关联触发器报错影响主业务触发器内嵌套事务或复杂查询精简触发器逻辑改为应用层约束组合中文乱码导致校验失败字符集不一致统一数据库字符集为UTF-8连接串指定编码还有一个容易被坑的地方使用DBLINK做跨库校验时如果源库字符集和目标库不一致中文长度可能对不上。解决办法是统一用LENGTHB或LENGTHC来对比不要直接用LENGTH因为有的字符集下LENGTH返回的是字符数有的返回字节数。最后再分享一个小技巧数据质量问题不要只盯着生产库测试环境同样要有数据质量规则。很多脏数据源头是在开发阶段业务逻辑不严谨导致的如果能在测试阶段就发现规则漏洞生产环境的数据质量会稳得多。我自己就是先写规则、再写业务代码每次迭代先用规则过上版数据发现异常直接打回开发修改后面维护成本低很多。数据质量这件事没有终点它是一个持续运营的过程。但把上面这5种方法落实到位至少能让日常数据维持在一个健康水平。你真去做了就会发现很多让人头疼的“数据疑难杂症”其实在源头就能避免。
返回列表