
我第一次被数据质量整到头皮发麻是在接手一套跑了两年多的YashanDB业务库之后。月末报表怎么都对不上总账查了一圈才发现资金流水表里混进了几十条金额字段为NULL的记录订单表里同一个客户ID下存在三条完全一样的下单记录业务系统却毫无感知。那一刻我认识到一个再简单的道理数据库选型再好、性能再高如果里面的数据是脏的一切都会跟着跑偏。YashanDB作为国产企业级数据库这些年越来越多地出现在核心交易、政务、金融等场景里它的SQL兼容性、分布式能力和高可用设计都没得说。但数据质量这事数据库本身只能提供工具真正决定质量的还是建表规范、写入逻辑、清洗策略和持续治理。这篇文章我会分享一套我自己在YashanDB环境里实际验证过的方法论一共五个方向覆盖约束设计、格式标准化、存量清洗、重复识别和监控闭环适合正在被脏数据折磨的DBA、数据开发以及需要直面业务表的后端工程师参考。1. 数据质量失控的根源脏数据从哪来代价有多大1.1 我在YashanDB线上库里见到的几类典型脏数据很多人以为脏数据就是字段是空的或者格式不对实际接触下来问题远不止这么简单。我在维护YashanDB实例时归纳过线上出现的数据质量缺陷基本可以划成五类质量维度典型表现常见来源完整性关键字段为NULL如订单金额、客户身份证号应用层漏传、接口中断、人工补录不规范唯一性同一业务实体出现多条重复记录并发提交、幂等缺失、多系统重复同步一致性同一条数据在不同表中口径不一致多套上游系统各自维护、同步规则混乱准确性数值越界、逻辑矛盾如负数的库存、子单金额大于主单校验缺失、手工改数、下游计算污染上游时效性数据长期不更新、状态停滞同步任务失败后未告警、数据接口断开这里面最坑的是口径不一致。比如客户表里手机号有的存11位有的存了带区号的格式性别字段有的是1/0有的是男/女还有的是M/F。当你写统计SQL时每种写法都能查出不同的结果对账对不上往往就是这类问题叠加出来的。1.2 数据质量差的代价远不止报表难看数据质量差的第一个代价是统计报表失真。领导看报表差一分钱都会追问而脏数据导致的口径漂移很难解释清楚。第二个代价是业务处理异常重复订单导致多发货、负数库存导致负卖这些都是真金白银的损失。第三个代价是系统之间对账失败YashanDB里主数据还算干净但同步到数据仓库或周边系统后因为字段标准不一致导致整天有人来问为什么两边对不上。更深层的问题是运维成本。当你不知道哪个数据是可信的每次排查问题都要把整条链路重新捋一遍一个简单的数据问题可能拖上半天。所以数据质量治理不是锦上添花它是数据库稳定运行、业务有序推进的地基。2. 方法一前置拦截——用约束、默认值与触发器把脏数据挡在门外2.1 约束设计一张业务表至少要配齐哪几把锁我最推荐的做法是在数据还没进表之前就把规则定死。YashanDB的SQL体系兼容主流关系型数据库语法建表时加上约束几乎是零成本的事但很多人因为图省事或怕影响性能把约束全省了结果脏数据长驱直入。以一张最简单的订单表为例下面这些约束应该一个都不能少CREATE TABLE t_order ( order_id NUMBER(20) NOT NULL, customer_id NUMBER(20) NOT NULL, order_amount NUMBER(14,2) NOT NULL, order_status VARCHAR2(20) NOT NULL, order_time DATE NOT NULL, channel_code VARCHAR2(10), CONSTRAINT pk_order PRIMARY KEY (order_id), CONSTRAINT chk_order_amount CHECK (order_amount 0), CONSTRAINT chk_order_status CHECK (order_status IN (待支付,已支付,已发货,已完成,已取消)) );NOT NULL保证完整性做统计时不至于被NULL值带偏主键保证唯一性从物理层面杜绝完全重复的订单IDCHECK约束保证准确性金额小于0的订单根本插不进表枚举类CHECK保证一致性状态只允许这几个合法值。外键约束同样重要。如果子表引用了主表不存在的客户ID这种孤儿数据就是典型的一致性缺陷。虽然外键在超高并发下有一定代价但在核心业务表上该加还是要加它挡住的是一整类数据问题。提示不要用应用层已经校验过了当理由省掉数据库约束。应用层校验只对走这个应用的请求有效而后台直改、数据同步、手工导入这些路径都可能绕过应用层只有数据库约束才是最后一道谁都绕不开的关卡。2.2 默认值与触发器处理漏传字段和隐含规则约束能拦住非法数据但很多字段不是非法而是该有却没人填。比如创建时间、最后更新时间、初次来源渠道这些字段应用层偶尔会漏传设一个合理的DEFAULT值是成本最低的兜底方案。CREATE TABLE t_customer ( customer_id NUMBER(20) NOT NULL, created_time DATE DEFAULT SYSDATE NOT NULL, source_channel VARCHAR2(20) DEFAULT UNKNOWN NOT NULL );YashanDB兼容模式里DEFAULT SYSDATE可以自动填充时间source_channel给一个UNKNOWN而不是NULL后续排查数据来源时就不会出现怎么查不到渠道的情况。触发器适合处理更复杂的写入规则。比如要求金额大的订单必须记录审批标记或者在删除主数据前自动检查是否存在关联子表。但触发器要克制地用尤其是高频写入表上滥用触发器会把写入链路搞得很重甚至拖慢并发。我的原则是能靠DEFAULT和CHECK解决的不用触发器触发器只解决多字段联动的写入逻辑。2.3 给存量表加约束的踩坑经历别在大流量时段硬来理论很美好现实是很多表已经跑了好几年想在存量表上加约束时往往会遇到一个尴尬局面数据本身已经不干净了加NOT NULL报错加UNIQUE报错。这时候不能硬加得先清洗再加固。另外给大表加约束时要注意锁的影响。我在一个订单流水表上加CHECK约束时赶上业务高峰期DDL一直等不到锁后续写入全部堵住差点引发线上故障。后来学乖了先评估表的数据量和写入活跃度尽量在业务低峰期操作YashanDB也会自动处理DDL锁的调度但仍建议在变更窗口执行并且分批验证约束的可行性先写出违规数据查询SQL确认查不到明细再加约束这样加约束的成功率会高很多也不会把并发扛压的库拖进死锁泥潭。3. 方法二格式与口径标准化——让看似一样的数据真正一样3.1 字符集与编码乱码不是玄学是规则没对齐多套系统往YashanDB同步数据时最容易踩的第一坑就是字符集。源端是GBK目标端是UTF8同步过程中如果没做转换中文会出现乱码更隐蔽的是看起来一样但实际字节不一样的半角全角问题比如手机号里的全角数字和半角138肉眼无法分辨但SQL比较时就是不一样。我的建议是接入数据前先做编码探测制定统一规则所有字符数据在YashanDB里一律使用UTF8编码同步任务里统一做编码转换对于全角半角、大小写、首尾空格等细节在接入层用标准化函数处理一遍。实践中可以用TRIM、UPPER和正则替换把文本先洗一遍UPDATE t_customer SET mobile REGEXP_REPLACE(TRIM(mobile), [[:space:]], ) WHERE mobile IS NOT NULL;这条SQL会把手机号里的首尾空格和中间不可见字符清掉。别小看这种操作我排查过好几次数据明明一样但关联不上的问题最后发现都是不可见字符惹的祸。3.2 日期、时间、数字、空值的统一规范日期字段是格式问题的重灾区。同一个业务系统里有人传2025-06-01 10:30:00有人传2025/06/01还有人存的是字符串20250601。在没有统一规范之前排序、区间查询全部可能出错。在YashanDB里我最常用的标准化手段是TO_DATE和TO_CHAR显式转换杜绝隐式依赖SELECT TO_CHAR(TO_DATE(2025/06/01, YYYY/MM/DD), YYYY-MM-DD) AS std_date FROM dual;给业务定的规矩很简单日期时间一律存DATE/TIMESTAMP类型不存字符串代码里所有日期比较都用TO_DATE转成日期类型再比较应用层传入的日期格式统一为YYYY-MM-DD HH24:MI:SS。空值的规范同样重要。我见过一张表中NULL、NULL字符串、空字符串三种空同时存在查询时每个都写一种条件漏一个就出错。规范就是没有值的字段一律存SQL的NULL不允许写空字符串占位更不允许把NULL当字符串写入。3.3 用字典表和值域映射统一业务口径第三种标准化是在元数据层面建字典表。比如性别与其让各系统传1/0男/女M/F不如统一存字典编码展示层再去翻译。我在项目里通常建一张t_dict表和一张t_dict_mapping表把业务字段与合法取值对应起来CHECK约束可以直接引用字典编码后续新增取值时只需要改字典表不需要改代码和表结构。这样做还有一个额外好处跨系统同步时以字典表为唯一标准任何来源的数据入库前先做一轮映射转换不一致的口径在源头就被消除后面写统计SQL时再也不用担心这个系统传的1到底是不是另一个系统传的男。4. 方法三SQL驱动的存量清洗——合规搞定历史脏数据4.1 清洗前先做数据体检写诊断SQL不凭感觉动手约束和标准化只管住新写入的数据历史存量数据还是要靠清洗来解决。清洗的第一步不是动手改数而是先做诊断。我在YashanDB里最常用的几类诊断SQL长这样查询缺失关键字段的记录数SELECT COUNT(*) FROM t_order WHERE order_amount IS NULL;查询重复记录SELECT customer_id, order_no, COUNT(*) FROM t_order GROUP BY customer_id, order_no HAVING COUNT(*) 1;查询越界数据SELECT COUNT(*) FROM t_order WHERE order_amount 0 OR order_time SYSDATE;诊断的原则是每条规则对应一条SQL每个结果都留下截图或者落表记录。这样清洗前就有了一份问题清单既能用来评估工作量也方便清洗后对照验证。我强烈建议把诊断SQL沉淀成一个质量检核脚本库以后每次大版本升级、数据迁移之后都能复用。4.2 清洗SQL实战NULL填充、格式规整、条件UPDATE诊断完就可以动手。最常见的清洗场景有三个对可推导的NULL值进行回填。比如渠道字段为NULL但日志表里有该客户的下单渠道可以通过关联UPDATE补上。回填前务必备份或记录影响行数。对格式混乱的字段做统一转换。把字符串日期转成DATE类型把全角数字统一为半角把英文枚举值翻译成字典编码。对越界数据做修正或打标。金额为负的根据业务规则判断是调整为正数还是标记为异常无法判断的宁可先置为业务认可的默认值并记入异常清单也不要直接删。做UPDATE清洗时我一直强调小步快跑——不要一次性UPDATE全表几百万行尤其不要写一条不带充分条件的UPDATE。可以按ID区间或按时间批次处理每批次控制在几千行以内观察执行计划和影响行数确认无误再跑下一批。这样即使某批出错影响面也可控。4.3 事务与备份清洗操作必须可回滚清洗是动真数据风险不亚于线上发布。我用YashanDB时养成了一套保命习惯清洗前先做逻辑备份或者把待清洗的主键和原始字段导出到一张备份表用事务包裹清洗SQL先在事务里执行并查询验证确认结果正确后再COMMIT每批次清洗后统计影响行数并与诊断SQL的问题清单比对看看是否完全覆盖清洗完成后重新跑诊断SQL确认问题数量归零。BEGIN UPDATE t_customer SET mobile REGEXP_REPLACE(TRIM(mobile), [[:space:]], ) WHERE mobile IS NOT NULL; UPDATE t_order SET order_status TRIM(order_status) WHERE order_status ! TRIM(order_status); COMMIT; END;这套流程看起来繁琐但能避免清洗反而破坏数据的事故。我见过有同事一条UPDATE把某个字段全表更新成同一个值如果没备份整个表就废了。可回滚是最低要求别在这上面省时间。5. 方法四重复与相似数据识别——合并去重的正确姿势5.1 精确去重用窗口函数找出保留行而不是随手DELETE先处理最简单的精确重复——多行数据的每个字段都完全一样。很多人第一反应是GROUP BY DELETE留下MIN(ID)但这种做法有时候会误伤关联数据。更稳妥的方案是用ROW_NUMBER()窗口函数给重复行编号先查出来哪些是要保留的行、哪些是候选删除行。SELECT order_id, order_no, customer_id, ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY order_id) AS rn FROM t_order;PARTITION BY order_no表示按订单号分组对组内重复的记录按order_id排序编号rn1的作为保留行rn1的就是重复行。先把这份结果落到一张临时表人工或半自动确认后再按order_id列表删除。之所以不建议直接DELETE是因为你需要留给人眼确认的机会。自动删除的脚本一旦写错条件删错数据是很难找回的特别是核心交易表宁可多查一次不要少看一眼。5.2 相似数据识别编辑距离、归一化与多字段打分精确去重只能解决完全一样的情况现实里更常见的是看似不同实则同一条。比如客户名称深圳市科兴信息技术有限公司和深圳科兴信息公司姓名里的张伟和张 伟这些在精确匹配下都不会被识别为重复但业务上可能就是同一个人。处理相似记录我的经验是分三步字段归一化去掉空格、统一大小写、把缩写展开成全称计算出相似度借助编辑距离函数或自定义存储函数给每条候选记录对比打分多字段交叉验证单一字段容易误判至少结合姓名手机号、公司名地址、身份证号姓名等多组权重来综合判断。如果YashanDB环境里没有现成的编辑距离函数可以用UTL_MATCH.EDIT_DISTANCE这类兼容函数或自己写一个PL/SQL函数来实现。重点不是算法多高级而是在业务可以接受的误差率内产生一份疑似重复清单交给业务方二次确认。清淤去重不可能100%自动化机器负责缩小范围人负责拍板。5.3 去重不只是删行关联数据合并与保留策略很多人去重时只顾着删主表的重复行忘了还有一堆子表引用着这些行的ID。如果父表删了某条记录子表的外键还指向它关联查询就全断了。我处理带关联关系的去重时流程通常是先选定保留的主记录然后把其他重复记录的关联业务归并到保留记录上最后才在确认无引用后删掉冗余记录。YashanDB的外键约束在这里会发挥保护作用如果直接删除有外键关联的父记录数据库会拒绝执行或触发级联操作这个报错其实是在提醒你别乱删。保留策略也有讲究。不是ID最小就一定最好要结合业务判断哪条记录是最完整的、最近更新的、来源最可信的。我在客户主数据合并时就遇到过保留的那条恰好是信息缺失的那条合并完反而更脏后来改成字段级取优的合并策略——不同来源的字段各取最优值合并成一条新记录整体质量才真正提升。6. 方法五监控指标与治理闭环——让数据质量可度量、可持续6.1 定义你的数据质量指标不要凭感觉说数据还行一次性清洗做完不难难的是防止新脏数据持续产生。我的经验是先把数据质量从抽象概念拆成可量化的指标再用人盯人的方式去巡检质量就会慢慢稳定下来。常用的质量指标可以分成五类指标项计算公式合格基线参考完整性1 - NULL数/字段总数关键字段100%非关键字段≥99%唯一性重复记录数/总记录数核心实体0重复准确性越界、矛盾记录数/总记录数≤0.01%一致性跨表单据不一致数/总记录数≤0.01%时效性超过N天未更新的记录数/总记录数按业务定义在YashanDB里这些指标可以用视图固化下来每天定时跑一遍把结果写进一张数据质量日志表。时间一长你手里就有了完整的质量趋势曲线哪个表在哪段时间变脏了都能看得出来。6.2 用定时任务质量检核SQL实现自动化巡检手工跑SQL巡检不可持续尤其是表多了以后。我在YashanDB上的做法是写一个质量巡检存储过程里面按业务优先级逐表跑检核规则每发现一类问题就插入一条质量日志。CREATE OR REPLACE PROCEDURE sp_dq_daily_check AS v_null_cnt NUMBER; BEGIN SELECT COUNT(*) INTO v_null_cnt FROM t_order WHERE order_amount IS NULL; INSERT INTO t_dq_log(check_date, table_name, check_item, bad_count) VALUES (TRUNC(SYSDATE), t_order, order_amount_null, v_null_cnt); COMMIT; END;然后通过JOB如DBMS_SCHEDULER兼容的调度机制每天凌晨自动执行。质量日志表里记录了每天的坏数据数量超过基线的部分会触发告警。如果你对环境里的数据库同步工具、ETL任务比较熟可以把同步任务失败导致的数据停滞也纳入巡检范围比如检查来源系统昨日有数据目标表今日却没有新增这类时效性异常。提示巡检存储过程要控制好资源消耗不要在业务高峰执行。我习惯把巡检放在凌晨低峰期且每个检核SQL只做COUNT或采样不跑全量扫描避免把生产库的I/O吃满。6.3 从发现到修复的责任闭环质量治理不是一次性运动指标和巡检只是发现问题的手段真正让数据质量持续提升的是问题从发现到关闭的闭环机制。我的团队约定了一套简单规则质量巡检发现的问题自动生成清单指派给对应表的所有者修复完成后更新处理状态写明原因和修复SQL月底统计一次各类问题的复发率。这套机制看起来不复杂但长期坚持下来效果很明显。以前脏数据是出了事才处理现在变成了每天自动体检、按单修复。对DBA来说最直观的好处是半夜再也不会被业务方的数据怎么又对不上了的求助电话吵醒因为告警和修复已经前置到了问题发生之前。7. 一次真实整改复盘从对不上账到量化治理7.1 场景背景这套系统的背景是一套基于YashanDB的订单交易库上游有3个业务系统持续同步数据另有一些历史数据是从老库迁移过来的。迁移时为了赶进度很多字段没有按规范校验日常同步又偶尔出现过任务中断和重复执行。结果就是订单表存在重复记录、金额字段存在NULL、客户手机号格式五花八门、状态字段几位数大小写混杂。当时业务方反馈的一个典型问题是查上个月的成交订单数三个系统跑出来三个数。7.2 五步落地过程整个整改按上面五个方法依次落地顺序很关键先做诊断基线跑完整套检核SQL输出问题清单评估影响范围再定约束方案和业务方确认每个字段的合法取值和必填规则形成建表和约束改造方案接着清洗存量按备份-事务-分批流程处理历史脏数据重点处理空值和重复同步标准化口径统一日期、编码、字典并把标准固化到同步任务的转换逻辑里最后上监控实现质量巡检存储过程和告警并定义每张核心表的质量基线。整个流程用了两周多其中清洗和确认业务口径耗时最长真正跑SQL的时间反而占比不大。这让我意识到数据质量工程最难的部分不是技术而是确认什么才算对。7.3 结果与心得整改完成后三套系统跑同一统计SQL出来的数字完全一致了重复订单清零金额NULL字段归零。后续月度巡检中核心表质量基线连续三个月保持在达标线以上。这中间也踩过一些坑比如曾有一批清洗SQL在同一事务里批量更新了几万行虽然结果没问题但因为开始前没有先小范围验证过程中提心吊胆后来我始终坚持先跑一万行看执行计划再看影响行数最后全量执行。最后再说一个实际体会数据质量不是一个一次性项目它更像数据库日常运行的一部分。约束、标准化、清洗、去重、监控这五件事里前两件是打地基中间两件是还债最后一件是防止再欠债。如果你手头的YashanDB库已经积累了一定规模的脏数据别急着一下子全改先把诊断SQL跑起来把问题清单列出来按影响面排优先级一步一步来数据质量是完全可以被管住的。