
数据质量这问题说大不大说小不小。做了这么多年数据相关工作见过太多系统上线时跑得通、一上量就崩或者报表对不上、领导拍桌子问“这个数到底准不准”的场面。国产数据库现在越用越多YashanDB这类产品在金融、政企、制造业里的落地场景也在快速增长数据质量的把控反而成了很多团队最挠头的事——不是没工具而是不知道从哪下手。我结合近几年在实际项目里折腾YashanDB的经验把它拆成五个可以落地的方向建表时的约束设计、事务与并发控制、存量数据清洗、日常质量巡检以及备份恢复兜底。不是说做完这五件事数据就一劳永逸而是说把这五个环节抓起来数据质量的下限就有了保障。这篇就按实操路子讲每步都配得上能直接抄的SQL和排查思路新手照着做能少踩坑老手也能看看有哪些细节被忽略了。1. 从schema设计抓起约束是数据质量的第一道防线很多团队的数据质量问题不是运行期产生的而是建表的时候就埋下了。最常见的就是“为了快”或者“怕麻烦”把该有的主键、非空约束全省了结果业务数据一进来重复、空值、脏数据全堆在库里后面再想补救成本翻十倍不止。1.1 主键与外键给数据立规矩YashanDB兼容Oracle语法建表的时候主键和唯一约束的写法跟传统关系型数据库差别不大但这里有个特别容易被忽略的点主键不只是“能查快”它更重要的是保证每一行都能被唯一标识。没有主键的表删改数据时连定位行都是个模糊操作更别提做数据对比和同步了。我见过一个实际案例某系统导入设备档案时没用主键结果同一条设备因为批量脚本重复跑了两次数据直接翻倍。后来排查半天只能靠“导入批次设备编码”这种业务字段组合去重折腾了两天才清理干净。如果建表时定义一个设备编号主键第二批次导入时直接就报主键冲突问题当场暴露。外键的争议稍大一些。很多工程师为了性能主动禁掉外键约束从纯OLTP吞吐角度看可以理解但代价是应用层的逻辑必须永远正确一旦写库顺序出错或者删了被引用的父记录那子表就变成“孤儿数据”关联查询全盘失控。我的习惯是核心业务表之间必须建外键至少也要建普通索引来承载关联查询。至于性能损耗通常小于后续手工修复数据造成的损失。YashanDB里用一条约束就能完成ALTER TABLE child_table ADD CONSTRAINT fk_child_parent FOREIGN KEY (parent_id) REFERENCES parent_table(id);1.2 唯一约束、非空与Check约束的应用技巧除了主键外键唯一约束、非空约束、Check约束这三样是数据质量的“隐形守门员”。它们的价值不是让SQL报错而是把错误拦截在数据进库之前。唯一约束用于业务上本就该唯一的字段比如身份证号、订单流水号、手机号。要注意的是YashanDB里唯一约束和唯一索引生成后如果业务上允许历史脏数据里存在多个NULL那约束默认是放行的因为NULL与NULL不相等。这时如果需要“一个客户只允许一个手机号为空”就要用函数唯一索引或触发器来兜底否则唯一约束形同虚设。非空约束通常配合默认值使用非常实用例如“创建时间”这类字段写上DEFAULT SYSDATE再配合NOT NULL应用程序就算忘了赋值也不会进空值。Check约束在国产数据库里用处其实比很多人想的大比如价格字段必须大于0、状态字段必须是固定枚举集合一条Check就能挡住业务层失效时的非法数据。在YashanDB中可以直接在建表语句中组合使用CREATE TABLE orders ( id NUMBER PRIMARY KEY, order_no VARCHAR2(64) NOT NULL UNIQUE, amount NUMBER(12,2) NOT NULL CHECK (amount 0), status VARCHAR2(16) DEFAULT NEW NOT NULL CHECK (status IN (NEW, PAID, SHIPPED, CANCELLED)), created_at TIMESTAMP DEFAULT SYSDATE NOT NULL );这样设计后乱写状态、负金额、重复单号的脏数据在入库之前就被数据库本身拦住了。很多团队觉得这类约束“限制了开发灵活性”但数据质量的本质本来就是对写入自由度的约束——先立规矩才能谈使用。2. 事务与并发控制守住数据一致性的底线约束管的是单条数据的合法性而数据一致性管的是多条数据之间的逻辑关系。YashanDB作为分析型与事务型兼顾的数据库在事务处理上的功底直接决定了并发场景下数据会不会错乱。这里最容易出问题的不是单行更新而是跨表、跨批次的操作。2.1 事务隔离级别怎么选关系型数据库的四种隔离级别——读未提交、读已提交、可重复读、串行化——在YashanDB里都能配置但不同场景的选择完全不一样。我见过不少团队直接把隔离级别拉到“串行化”图省心结果并发一上来锁等待和死锁把系统拖垮也见过为了性能调到“读未提交”报表经常读到中间状态的数据最后对账对不上。默认情况下YashanDB的事务隔离级别比较接近Oracle的“读已提交”这也是我认为多数业务场景的平衡点查询只看到已提交的数据不会读到写了一半的中间状态。如果业务要求在一个事务里反复查同一张表必须看到完全一致的结果那才需要考虑可重复读或显式加锁。这里给一个小建议不要全局改隔离级别而是在特定事务里精确控制。比如资金扣减这类操作可以给关键行加上SELECT FOR UPDATE锁而不是让所有查询都背隔离级别的成本。在YashanDB中示例写法如下BEGIN SELECT balance INTO v_balance FROM accounts WHERE account_id :acc_id FOR UPDATE; IF v_balance :amount THEN UPDATE accounts SET balance balance - :amount WHERE account_id :acc_id; ELSE RAISE_APPLICATION_ERROR(-20001, 余额不足); END IF; COMMIT; END;2.2 并发场景下的死锁与锁等待排查并发控制做不好最典型的现象有两个锁等待超时和死锁。锁等待通常是一条事务拿住锁后长时间不提交把后面所有相关操作都堵住了。排查时第一步就是看会话和锁状态YashanDB提供了锁相关的视图例如V$LOCK。先找出阻塞链再用V$SESSION定位卡住的SQL和应用会话。这里有个经验90%的锁等待问题不是数据库不行而是应用代码忘了提交事务。尤其是用了数据库连接池的情况下连接归还池子前如果事务没回滚或提交就会带着锁一起还回去下一个拿到这个连接的请求就莫名其妙被阻塞。所以排查锁问题先查应用日志再查数据库会话状态不然容易白忙活。死锁的处理思路其实也清晰数据库检测到死锁后会自动回滚其中一个事务这时应用要捕获死锁错误并做好重试机制。我参与过的项目里处理死锁的标准动作是——把更新操作按固定顺序执行让所有事务都按同一路径拿锁死锁概率能降一个量级。另外事务里不要做远程调用或慢查询锁持有时间越短并发越稳。3. 存量数据清洗与规范化给历史数据做一次大扫除约束和并发只能保证“以后进库的数据是好的”但存量数据里的重复、空值、格式混乱怎么办这时候就需要做一次系统性的数据清洗。清洗不是瞎删数据而是有条理地识别、标记、合并和修正。3.1 去重与冗余识别用ROW_NUMBER精准定位去重是清洗里的头号任务。重复数据通常分为完全重复和业务关键字段重复两类。完全重复好办按所有字段分组HAVING COUNT(*) 1就能找出来但更常见的是“业务上重复”——客户ID不同但身份证号相同订单编号不同但交易流水号相同。这种就一定要靠业务唯一键来识别。YashanDB里最优雅的去重定位方式是窗口函数ROW_NUMBER()。比如要把同一身份证号下最早一条记录保留其余标记为脏数据可以这样写SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER ( PARTITION BY id_card_no ORDER BY created_at ASC, id ASC ) AS rn FROM customers t ) t WHERE rn 1;把rn 1的数据导出人工确认后再用关联删除或“并入主记录”的方式处理。这里一定要注意清洗之前必须先备份把要删除的行主键列表单独存一张表哪怕误删了也能恢复。清洗冗余字段是另一个容易被忽视的点。比如客户表里同时存在“所在省”“所在市”“省市区全称”三个字段那就要确认以哪个字段为准另一个通过程序回写避免同一信息多份存储、互相矛盾。这种数据不一致是数据质量评估里典型的一类也是后面报表对不上的常见原因。3.2 字段格式规范化日期、编码、空值一个都不能漏格式规范化的核心是“一套数据一套标准”。最让人头疼的是日期字段有的是2024-08-15有的是2024/08/15还有的存成字符串20240815。YashanDB支持标准日期类型清洗时尽量用TO_DATE把字符串统一转成DATE或TIMESTAMP。转换失败的记录单独落表别让整批清洗脚本因为一行脏数据崩溃。sql -- 找出格式异常的日期避免转换崩溃 SELECT id, raw_date FROM raw_data WHERE raw_date IS NOT NULL AND TO_DATE(raw_date, YYYY-MM-DD) IS NULL;空值处理也一样。很多系统里空值有四种表达NULL、空字符串、字符串“NULL”、字符串“N/A”。这四种在逻辑上其实都是“不知道”但它们会导致GROUP BY、COUNT、JOIN的结果完全不同。清洗时统一转成真正的NULL并对业务核心字段执行非空校验这步做完后统计口径才谈得上准确。我的建议是清洗动作一定要做成“可重复执行”的脚本而不是一次性手工操作。把清洗逻辑固化到存储过程或定时任务里即便以后再有脏数据进来也能用同一套规则自动处理这样数据质量是持续向好的。4. 用质量校验SQL与巡检机制把问题暴露在早期数据质量问题最怕的不是存在而是不知道、发现晚。所以日常巡检机制是数据质量的第四根支柱。巡检不是简单SELECT COUNT(*)看行数而是用一套带业务规则的SQL把可疑数据主动捞出来。4.1 质量规则SQL模板照抄就能用我习惯把数据质量规则分成四层完整性、唯一性、有效性、一致性。每一层都可以用SQL直接表达下面这几个模板基本能覆盖80%的场景。完整性检查的核心是空值率。对关键业务字段做空值统计超过阈值就要告警SELECT COUNT(*) AS total_rows, COUNT(order_no) AS with_order_no, COUNT(customer_id) AS with_customer_id, ROUND((COUNT(*) - COUNT(order_no)) * 100.0 / COUNT(*), 2) AS null_rate_order_no FROM orders;唯一性检查就是查重复。有效性和一致性检查则是贴业务逻辑的比如“订单金额不能为负数”“发货日期不能早于下单日期”这些都可以写成专门的校验视图SELECT order_id, create_date, ship_date FROM orders WHERE ship_date create_date;建议把这类查询收集起来统一放在一个叫quality_check的视图集合里。每次巡检只需要刷新这些视图把结果行数大于0的规则记录下来即可。4.2 把巡检做成定时任务配合告警推送有了校验SQL剩下的就是让它定期跑。YashanDB的作业调度功能可以建定时任务把校验SQL的结果输出到一张质量日志表。这张日志表记录每次巡检的时间、检查项名称、异常行数、异常明细方便留痕。再配合自动化运维平台的告警通道异常数据一到阈值就推消息给相关负责人。这里有个实操细节巡检任务不要放在业务高峰期跑尽量选凌晨低峰时段。质量巡检本身会消耗资源尤其是全表扫描类校验放在高峰可能影响在线交易。我见过有团队把巡检放在白天跑结果一个全表扫描查询把CPU打满业务延时报错得不偿失。巡检频率也分等级核心表订单、账户、库存建议每天校验一次维表和配置表可以三天或一周一次。对于异常数据不要只记日志要配套一个“质量问题工单”流程也就是每条异常都有人认领、有人修复、有人复核形成闭环。这样数据质量的提升才是可持续的。5. 备份恢复与数据同步最后一道兜底防线数据质量再怎么防也防不住人为误操作、硬件故障、恶意删除这些黑天鹅。YashanDB作为国产数据库在备份和恢复方面有自己的工具链但如果平时不演练、不校验真出事时备份能不能用就是个未知数。所以我把备份恢复也列进数据质量的五大方法里——没有可靠恢复能力数据库的“数据可用性”根本无从谈起。5.1 备份完整性验证与恢复演练常见的备份方式有逻辑备份导出DMP或SQL文件和物理备份数据文件快照式备份。逻辑备份适合迁移和部分恢复物理备份适合快速全量恢复两者是互补关系不是二选一。很多团队做了定时备份但从不测试恢复等到数据库真的损坏才手忙脚乱恢复结果发现备份文件早就坏了或备份不完整。我现在坚持一个原则每次备份完成必须做“恢复验证”——把备份文件还原到一个隔离环境启动数据库实例执行ANALYZE TABLE和相关完整性检查。这一步看着费时间但在事故发生时能省下几十个小时。-- 恢复完成后两步确认备份可用性 ANALYZE TABLE orders COMPUTE STATISTICS; SELECT COUNT(*), MIN(created_at), MAX(created_at) FROM orders;恢复演练的频次至少一季度一次。对于金融级场景建议每月一次。演练内容不只是“能启动数据库”还要验证业务登录、关键查询、重要报表能正常跑才算真正备份可用。5.2 数据同步工具与跨环境一致性校验除了备份数据同步也是数据质量和可用性的重要环节。主备集群、读写分离、数仓抽取都离不开同步工具。YashanDB生态里比较常见的做法是使用官方同步组件配合第三方数据同步平台实现异构数据库之间的数据流转。数据同步最容易出现的问题是“漏数据”和“延迟”。漏数据通常是同步中断后没有续传延迟通常是网络带宽或目标库写入瓶颈。针对这两个问题最有效的抓手是“数据对账”。每次同步任务结束后对源库和目标库做关键表的行数核对以及关键字段的校验和核对不一致就自动触发告警。-- 简易对账两端各自计算并比较校验和 SELECT COUNT(*) AS row_cnt, NVL(SUM(DBMS_OBFUSCATION_TOOLKIT.MD5( order_id || | || amount || | || status )), EMPTY) AS checksum FROM orders;注意DBMS_OBFUSCATION_TOOLKIT属于Oracle风格语法YashanDB的实际函数名请以当前版本文档为准但这种“行数关键字段校验和”的对比思路是通用的。对账一旦发现不一致先暂停同步任务找出差异范围再决定用增量补充还是全量重导。做对账时还要关注延迟数据同步的延迟分钟数要纳入监控超过阈值必须告警因为“时间滞后”本身就是数据质量不达标的表现。6. 常见问题与排查技巧实录把踩过的坑变成经验方法讲完了我把自己在实际运维YashanDB过程中遇到的数据质量相关问题和排查经验整理成速查表大家遇到类似情况可以参考。6.1 常见数据质量问题对照表现象可能原因排查思路解决建议主键冲突或数据重复入库缺少唯一约束或批量脚本重复执行查业务唯一字段是否有约束查导入任务日志加唯一约束和索引导入时按业务键做存在性判断报表统计数据与业务系统不一致存在空值或多字段冗余表达检查空值率、GROUP BY口径、字段标准统一空值表达清洗冗余字段建立统计口径文档锁等待严重接口超时长事务未提交、连接池复用未清理查V$LOCK/V$SESSION阻塞链优化事务边界确保连接归还时提交或回滚按固定顺序更新备份文件无法用于恢复备份后未验证、备份文件损坏尝试在隔离环境恢复建立备份后恢复验证流程定期演练同步后源库和目标库数据不一致同步中断未续传、目标库写入失败对账行数和关键字段校验和增加自动对账与告警同步失败自动重试日期字段格式混乱导致排序或转换失败历史导入时未统一格式用TO_DATE转换捞取异常值制定格式标准清洗存量约束新写入6.2 排查步骤的经验总结排查数据质量问题时我建议大家按照“先定位影响范围再定位规则最后定位根因”的顺序来。比如发现某个报表数据不对先看是单张表的数据异常还是多表关联后的逻辑异常。单表问题查数据本身的完整性和唯一性多表问题查关联字段的值域是否匹配比如两个表里的客户ID编码规则是否一致一个用C001另一个用1001这种隐性问题光看SQL很难发现要靠实际数据抽样才能看出来。实际排查中我还总结出一个有用的习惯给每张核心表建立一个“数据血缘”清单记录数据从哪来、经过哪些转换、到哪里去。数据出问题时顺着血缘就能快速定位是哪一环出了岔子而不用一个个查询去猜。另一个容易被忽略的排查点是“时间上下文”。很多数据质量问题其实是历史数据遗留的比如某字段在改了业务规则之后新数据合法、旧数据不合法。这时候不要急着改数据库要先确认业务规则变更的生效时间节点把新旧数据分开处理。清洗逻辑务必带上生效时间条件否则容易把历史正常数据当脏数据一起清掉那就真是好心办坏事了。我个人在实际操作中的体会是数据质量这件事不存在一劳永逸的完美方案更重要的是把规则固化下来、把巡检跑起来、把问题闭环掉。每解决完一个数据质量问题就补一条校验规则、加一处注释、沉淀一段文档这套体系就会越来越完善。等到哪天别人问起你们数据库的数据质量怎么样你能直接报出校验覆盖率、空值率、异常处理闭环率这些数字那时候才是真正心里有底。上面这些方法和SQL只要有环境随时可以验证建议先拿测试库跑一遍把套路摸熟了再上生产。