先说个不太体面的现场。晚上十一点迁移群里还在刷消息。业务问“今晚能不能切”开发回“存储过程那边还有点问题。”DBA发了张截图执行计划看着不太对但谁也说不清是统计信息没跑还是索引建错了。以前这种时候我大概率会跟着急现在我会先问一句KDMS那版评估报告在哪高风险对象今天复核过没有。我越来越觉得数据迁移工具最大的用处不是把数据从左库搬到右库而是搬之前就把话说明白哪些对象基本能走哪些要改改起来是半天还是一周哪些地方看着能迁、上线后会咬人。电科金仓KDMS干的就是这件事。它把源库里的表、视图、索引、约束、存储过程、函数、触发器、作业这些东西采出来按规则过一遍给出兼容度、改造动作、风险等级、工作量这些指标。听起来像报表真用起来是“救命绳”——至少能在评审会上少吵半小时。这篇文章我不打算写成产品说明书。我按自己做项目时的顺序来先评估再建表再跑增删改查再做校验最后复盘哪些坑是报告提前拦住的哪些坑还是漏了。代码都是示意骨架别直接照搬生产但思路可以直接拿去用。评估最难的地方是“差不多”三个字太便宜很多迁移项目的评估表面上有文档实际上只有规模多少表、多少存储过程、数据量几T、停机窗口多久。这个信息不是没用是远远不够。规模只能告诉你“活儿大”不能告诉你“活儿险”。我以前吃过亏。某次异构迁移评审会上大家说视图不多业务也说不复杂。真到转换才发现一个视图套了四层里面还藏着同义词再往下追同义词指向另一个 schema 的函数函数里又调了作业。单看每个对象都不算大串起来就是一团线。最后不是迁移工具不行是我们事前没把这团线拆开。KDMS的价值就在“拆开”。它评估的不只是某个对象能不能转还会把依赖、命中规则、风险等级摆出来。比如一张表被判 REWRITE原因可能不是表本身多难而是默认值带了自定义函数一个存储过程被判 REDESIGN可能不是因为语法而是里面有自治事务、动态 SQL、外部调用。这个颗粒度很重要。没有它评审会很容易变成感觉之争甲方觉得开发悲观开发觉得业务天真DBA夹在中间背锅。所以我要求项目组把KDMS报告拆成两张口径。一张给管理层看按对象类型汇总表多少、视图多少、存储过程多少PASS 多少、REWRITE 多少、REDESIGN 多少总工作量多少人日。另一张给干活的人看只挑 HIGH、MANUAL、阻塞依赖按业务域分组直接进看板。别把所有对象都塞进周会没人看完还显得你不聚焦。我们自己一般会落一套台账不为替代KDMS只为把评估结果管起来。批次号要留住因为评估会变。今天规则更新昨天 HIGH 的可能降成 MID也可能开发复核后发现KDMS标 PASS 的对象业务语义上其实不能直迁。台账就是用来留痕的。CREATE TABLE mig_project ( project_id BIGINT NOT NULL, project_name VARCHAR(128) NOT NULL, source_env VARCHAR(64) NOT NULL, target_env VARCHAR(64) NOT NULL, assess_batch_no VARCHAR(32) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT pk_mig_project PRIMARY KEY (project_id) ); CREATE TABLE mig_object_inventory ( inv_id BIGINT NOT NULL, project_id BIGINT NOT NULL, assess_batch_no VARCHAR(32) NOT NULL, schema_name VARCHAR(64) NOT NULL, object_name VARCHAR(128) NOT NULL, object_type VARCHAR(32) NOT NULL, rows_estimate BIGINT, size_estimate_mb NUMERIC(18,2), convert_action VARCHAR(16) NOT NULL, -- PASS / REWRITE / REDESIGN / MANUAL risk_level VARCHAR(8) NOT NULL, -- LOW / MID / HIGH workload_md NUMERIC(10,2), kdms_rule_hit VARCHAR(256), assess_comment VARCHAR(512), CONSTRAINT pk_mig_object_inventory PRIMARY KEY (inv_id), CONSTRAINT uq_mig_obj UNIQUE (assess_batch_no, schema_name, object_name, object_type), CONSTRAINT fk_mig_inv_project FOREIGN KEY (project_id) REFERENCES mig_project(project_id) ); CREATE TABLE mig_object_dependency ( dep_id BIGINT NOT NULL, inv_id BIGINT NOT NULL, depend_on VARCHAR(256) NOT NULL, depend_type VARCHAR(32) NOT NULL, is_blocking CHAR(1) NOT NULL DEFAULT N, CONSTRAINT pk_mig_dep PRIMARY KEY (dep_id), CONSTRAINT fk_mig_dep_inv FOREIGN KEY (inv_id) REFERENCES mig_object_inventory(inv_id) );评估批次进来以后我会先插项目再插对象。老的批次别急着删。删历史很容易找回当时的判断理由很难。INSERT INTO mig_project(project_id, project_name, source_env, target_env, assess_batch_no) VALUES (2026091301, 账务核心异构迁移, billing_oltp, kes_core, KDMS-B20260913-01); INSERT INTO mig_object_inventory( inv_id, project_id, assess_batch_no, schema_name, object_name, object_type, rows_estimate, size_estimate_mb, convert_action, risk_level, workload_md, kdms_rule_hit, assess_comment ) VALUES (1, 2026091301, KDMS-B20260913-01, BILL, ACCT_BALANCE, TABLE, 86000000, 18432.00, PASS, LOW, 0.50, TABLE_BASIC_PASS, 范围分区迁移后重建本地索引), (2, 2026091301, KDMS-B20260913-01, BILL, ACCT_DAILY_SUM, TABLE, 12500000, 4096.00, REWRITE, MID, 2.00, DEFAULT_FUNC_NOT_AUTO, 自定义函数默认值需要改造), (3, 2026091301, KDMS-B20260913-01, BILL, V_ACCT_FLOW, VIEW, NULL, NULL, REWRITE, MID, 1.50, VIEW_NESTED_LEVEL_GT3, 嵌套偏深展开后逐层核语义), (4, 2026091301, KDMS-B20260913-01, BILL, P_RATED_INSERT, PROC, NULL, NULL, REDESIGN, HIGH, 8.00, PROC_AUTONOMOUS_TRANS, 自治事务先拆补偿再谈割接), (5, 2026091301, KDMS-B20260913-01, BILL, TR_ACCT_AUDIT, TRIGGER, NULL, NULL, MANUAL, HIGH, 5.00, TRIGGER_MULTI_TABLE_WRITE, 跨表写放大考虑改补偿任务); UPDATE mig_object_inventory SET risk_level MID, workload_md 3.00, assess_comment 复核后自治事务可拆到补偿表风险先降一档 WHERE assess_batch_no KDMS-B20260913-01 AND object_name P_RATED_INSERT; DELETE FROM mig_object_dependency WHERE inv_id IN ( SELECT inv_id FROM mig_object_inventory WHERE assess_batch_no KDMS-B20260913-01 AND object_name LEGACY_DUMMY_PROC );平时我跑得最多的是两条统计。第一条给项目会看别整太花直接看哪类对象吃掉最多人力。第二条给自己和核心开发看只盯高风险和阻塞依赖。SELECT object_type, COUNT(*) AS total_cnt, SUM(CASE WHEN convert_action PASS THEN 1 ELSE 0 END) AS pass_cnt, SUM(CASE WHEN convert_action REWRITE THEN 1 ELSE 0 END) AS rewrite_cnt, SUM(CASE WHEN convert_action REDESIGN THEN 1 ELSE 0 END) AS redesign_cnt, SUM(CASE WHEN convert_action MANUAL THEN 1 ELSE 0 END) AS manual_cnt, ROUND(SUM(workload_md), 2) AS workload_md FROM mig_object_inventory WHERE assess_batch_no KDMS-B20260913-01 GROUP BY object_type ORDER BY workload_md DESC, total_cnt DESC; SELECT i.schema_name, i.object_name, i.object_type, i.risk_level, i.workload_md, d.depend_on, d.depend_type, d.is_blocking FROM mig_object_inventory i LEFT JOIN mig_object_dependency d ON d.inv_id i.inv_id WHERE i.assess_batch_no KDMS-B20260913-01 AND (i.risk_level HIGH OR d.is_blocking Y) ORDER BY i.risk_level DESC, i.object_type, i.object_name;我特别喜欢看 SUM(workload_md) DESC。原因很简单吵架的时候人会说“这个不难”数字不会。它不一定准但比拍脑袋强。报告看完别急着动工建表这一步要收着点评估报告出来后最容易犯的错是兴奋。一看百分之九十能迁团队马上冲。我建议缓一缓。迁移目标库不是源库的复印件。尤其跑了很多年的账务库索引、触发器、历史作业堆得像仓库什么都搬过去等于把陈年灰尘也打包。我的原则有点朴素能直迁的别加戏要改的别硬塞回旧结构。比如源表四十个索引迁移后真需要的可能不到一半。评估报告里对索引、约束、触发器的结论正好用来做取舍保留、合并、重建、废弃。别心疼旧索引不会给你发奖状它只会拖写入。建表演示我用账务场景写。重点不在语法而在把报告里的风险落成约束金额定点数状态收窄流水号唯一审计时间默认分区键和业务主键分开。尤其最后这点账务表里很多人喜欢拿业务单号当主键再拿日期分区搞到最后唯一性、分区裁剪、批量更新互相打架迁移后批量作业一跑就露馅。CREATE TABLE acct_balance ( acct_id BIGINT NOT NULL, balance_date DATE NOT NULL, currency_code CHAR(3) NOT NULL DEFAULT CNY, balance NUMERIC(18,2) NOT NULL DEFAULT 0 CHECK (balance 0), frozen_amt NUMERIC(18,2) NOT NULL DEFAULT 0 CHECK (frozen_amt 0), version_no BIGINT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT pk_acct_balance PRIMARY KEY (acct_id, balance_date) ) PARTITION BY RANGE (balance_date); CREATE TABLE acct_balance_202609 PARTITION OF acct_balance FOR VALUES FROM (2026-09-01) TO (2026-10-01); CREATE TABLE acct_flow ( flow_id BIGINT NOT NULL, acct_id BIGINT NOT NULL, flow_type VARCHAR(16) NOT NULL CHECK (flow_type IN (RECHARGE,DEDUCT,FREEZE,UNFREEZE,ADJUST)), amount NUMERIC(18,2) NOT NULL CHECK (amount 0), flow_status CHAR(1) NOT NULL DEFAULT N CHECK (flow_status IN (N,P,F,C)), request_no VARCHAR(64) NOT NULL, biz_time TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT pk_acct_flow PRIMARY KEY (flow_id), CONSTRAINT uk_acct_flow_req UNIQUE (request_no) ); CREATE INDEX idx_acct_flow_acct_time ON acct_flow(acct_id, biz_time); CREATE INDEX idx_acct_flow_status_time ON acct_flow(flow_status, biz_time);这里多说一个报告里常见但容易被低估的点自定义函数默认值。源表允许 DEFAULT some_func()迁移时别随手改成触发器“差不多就行”。账务系统里我更偏向把默认值收掉写入路径显式给值。看起来啰嗦实际清楚。历史包袱该进改造清单就进清单不要让它混进生产结构里等出了对账差异再回头找那个成本更高。还有触发器。评估报告如果提示跨表写、写放大、时序复杂我一般先问业务这个触发器当年为了解决什么很多答案早就没人记得了。没人记得不代表能删但至少说明它该被当成 MANUAL 对待而不是 PASS 后面的一个勾。增删改查演练别把正确性只交给 count(*)割接演练里我最不信“count 一致所以没问题”。账务系统不是仓库盘点少两箱货能看出来账务是状态机条数对了状态错了更危险。比如一笔扣费从 N 到 P 再到 F金额、余额、冻结额、审计时间只要有一个环节偏了 count 照样漂亮账已经不对了。所以演练我会准备几类典型 DML正常入账、重复请求、冻结解冻、冲正、批扣、审计更新、失败回滚。下面的代码不追求复杂重点看事务边界、幂等、乐观锁和分批。异构迁移里很多“迁移后变慢”不是工具问题是大事务、锁竞争、执行计划一起冒头。-- 同一请求号只入账一次演练重复提交 MERGE INTO acct_flow f USING ( SELECT 900001 AS flow_id, 10086 AS acct_id, RECHARGE AS flow_type, CAST(100.00 AS NUMERIC(18,2)) AS amount, N AS flow_status, REQ-20260913-0001 AS request_no, TIMESTAMP 2026-09-13 20:30:00 AS biz_time ) s ON (f.request_no s.request_no) WHEN NOT MATCHED THEN INSERT (flow_id, acct_id, flow_type, amount, flow_status, request_no, biz_time) VALUES (s.flow_id, s.acct_id, s.flow_type, s.amount, s.flow_status, s.request_no, s.biz_time); -- 余额更新带版本号防止两个会话把余额改穿 UPDATE acct_balance SET balance balance 100.00, version_no version_no 1, updated_at CURRENT_TIMESTAMP WHERE acct_id 10086 AND balance_date DATE 2026-09-13 AND version_no 7 AND balance 100.00 0; -- 冻结操作必须整体成功或整体回滚 BEGIN; UPDATE acct_balance SET balance balance - 30.00, frozen_amt frozen_amt 30.00, version_no version_no 1, updated_at CURRENT_TIMESTAMP WHERE acct_id 10086 AND balance_date DATE 2026-09-13 AND version_no 8 AND balance 30.00; INSERT INTO acct_flow(flow_id, acct_id, flow_type, amount, flow_status, request_no, biz_time) VALUES (900002, 10086, FREEZE, -30.00, N, REQ-20260913-0002, CURRENT_TIMESTAMP); -- 如果上面 UPDATE 影响行数不是 1不要补写一条反向流水硬找平直接 ROLLBACK 查原因 COMMIT;批扣这块我多说两句。迁移后的第一个账期最容易出事。不是逻辑错是并发一上来老系统靠“运气加索引”能过新库把问题放大了。批处理别一口气吞太多分批提交锁竞争也好看一点。DO $$ DECLARE v_batch_size INT : 5000; v_affected INT : 0; BEGIN LOOP WITH cand AS ( SELECT acct_id, balance_date FROM acct_balance WHERE balance_date DATE 2026-09-13 AND balance 10 ORDER BY acct_id LIMIT v_batch_size FOR UPDATE SKIP LOCKED ) UPDATE acct_balance b SET balance b.balance - 10, version_no b.version_no 1, updated_at CURRENT_TIMESTAMP FROM cand WHERE b.acct_id cand.acct_id AND b.balance_date cand.balance_date; GET DIAGNOSTICS v_affected ROW_COUNT; EXIT WHEN v_affected 0; COMMIT; END LOOP; END $$;跑完 DML我一般会顺手做三件小事看锁等待看是否有死锁日志把高频 SQL 拉出来重看执行计划。听着琐碎真出事时就知道值钱了。迁移后很多人忘了一个动作重新分析表。新库刚灌完数据统计信息不准优化器判断偏了执行计划很容易翻车。ANALYZE acct_balance; ANALYZE acct_flow; EXPLAIN (ANALYZE, BUFFERS) SELECT acct_id, biz_time, flow_type, amount FROM acct_flow WHERE acct_id 10086 AND biz_time TIMESTAMP 2026-09-13 00:00:00 ORDER BY biz_time DESC LIMIT 100;校验别做成马拉松分层跑才有时间睡觉校验这事我很怕两种极端。一种是太粗只比 count心里还安慰自己“反正条数一样”。另一种是太狠全库所有字段所有行硬扫扫到业务来催还没扫完。大一点的系统割接窗口根本经不起这么折腾。更实际的做法是分层。第一层看总量和边界确认没大片丢、没大片多。第二层看关键口径尤其金额、状态分布。第三层抽样明细挑高风险链路逐笔核。第四层把 KFS 这类同步链路里的校验能力用起来尽量靠近增量末端比对别等全量结束才发现前面的差异已经滚了很多轮。下面这套 SQL 不追求“一键证明全库一致”它只回答三个问题有没有漏有没有多关键口径偏没偏。割接前跑一次追平后跑一次割接后复跑一次。结果进差异表别只在聊天里发一句“有个差异”。-- 总量和边界 SELECT ACCT_BALANCE AS object_name, MIN(acct_id) AS min_acct, MAX(acct_id) AS max_acct, COUNT(*) AS row_cnt, SUM(balance) AS sum_balance, SUM(frozen_amt) AS sum_frozen FROM acct_balance WHERE balance_date DATE 2026-09-13; -- 状态分布看语义有没有漂 SELECT flow_status, COUNT(*) AS cnt, SUM(amount) AS amt FROM acct_flow WHERE biz_time TIMESTAMP 2026-09-13 00:00:00 AND biz_time TIMESTAMP 2026-09-14 00:00:00 GROUP BY flow_status ORDER BY flow_status; -- 差异表宁可笨一点也要能追踪 CREATE TABLE verify_diff ( diff_id BIGINT NOT NULL, check_batch VARCHAR(32) NOT NULL, pk_value VARCHAR(128) NOT NULL, object_name VARCHAR(64) NOT NULL, diff_column VARCHAR(64) NOT NULL, source_value VARCHAR(256), target_value VARCHAR(256), diff_type VARCHAR(16) NOT NULL, -- MISSING / EXTRA / COLUMN / ACCEPT found_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT pk_verify_diff PRIMARY KEY (diff_id) ); INSERT INTO verify_diff(diff_id, check_batch, pk_value, object_name, diff_column, source_value, target_value, diff_type) VALUES (1, CHK-20260913-01, 10086, ACCT_BALANCE, balance, 188.00, 168.00, COLUMN); UPDATE verify_diff SET diff_type ACCEPT WHERE check_batch CHK-20260913-01 AND pk_value 10086 AND diff_column balance AND source_value 188.00 AND target_value 168.00;差异处置要有脾气也要有规矩。能定位到时点的按同步时点补定位不到的进人工台账涉及金额和状态的我不接受“两边都改一下”这种和稀泥。迁移里最危险的不是发现差异是差异没人认领。KDMS负责事前把风险亮出来KFS负责同步过程里把不一致尽量早暴露中间这套差异表负责闭环。少一环晚上都睡不踏实。报告不是终点演练也别只演成功我一般要求KDMS评估后项目组必须交三样东西。第一对象级清单直接进看板别停留在PDF。第二风险热力按业务域标红专门留给架构评审。第三工作量口径把 PASS、REWRITE、REDESIGN、MANUAL 换算成人日再乘一个排期风险系数。系数别装精确项目紧就高一点团队熟就低一点但要写出来。写不出来的风险最后都会变成邮件里的“当时没想到”。还有两条红线我会提前放到群里。被标 REDESIGN 的对象不允许迁移当天顺手改。要改就独立提测单独给回退方案。依赖外部资源的对象比如远程连接、外部表、第三方接口、消息队列必须显式登记不许藏在存储过程深处。很多迁移事故看着像数据库问题根子在外面工具看不见人就得补上。最后是演练而且要带伤演练。别挑一个风平浪静的周末跑 happy path。故意断一次同步故意让增量晚到故意制造主键冲突故意把磁盘水位顶上去。不是折腾人是验证流程。KDMS把风险提前摆上桌面KFS把链路跑起来剩下真出事那一秒团队要知道先查哪张表、先锁哪个动作、先给谁打电话。这个本事报告替代不了工具也替代不了。写到这回到开头那个晚上。我的答案通常不是“能切”或“不能切”而是高风险对象还剩四个其中两个有回退方案两个没有阻塞依赖登记了三条业务确认一条校验差异还剩七笔五笔已闭环。然后让能做决定的人做决定。数据迁移工具把人从“猜”里捞出来但不能替人拍板。它最实在的意义是让那句“应该差不多”再也混不过去。
企业数字化 ERP 产品动态
相关推荐
Apache Beam GCP 安全日志分析器:从 Log Sink 配置到每周 IAM 安全告警的自动化实践 大数据批处理流处理数据工程 【免费下载链接】beam Apache Beam is a unified programming model for Batch and Streaming data processing. 项目地址: https://gitcode.com/gh_mirrors/beam4/beam 点击查看 免费下载 Apache Beam 仓库的 infra/security 模块 实现… · 2026/9/25 17:46:38
将 Hunk 接入 Git:pager 与 difftool 完整配置指南 开发工具代码评审CLIAI 应用 【免费下载链接】hunk Review-first terminal diff viewer for agentic coders 项目地址: https://gitcode.com/gh_mirrors/hu/hunk 点击查看 免费下载 Hunk 是一款面向 Agent 化开发者的终端 diff 查看器(Review-first ter… · 2026/9/25 17:46:08
万能文件分析师Skill:以后收到几十页PDF,别再从第一页开始硬看了! 工作里最容易被低估的一件事,就是“看文件”到底有多耗时间。领导突然丢来一份80页的PDF,让你下午给结论;群里一次发来5个方案,让你说说区别;同事传来几十页制度文件,让你找出跟某个项目有关的条款。过去我… · 2026/9/25 18:26:59
你不知道的Python开发工具配置:TaoToken统一Key接入PyCharm与Vim /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 18:26:28
【WPF-VisionMaster】机器视觉通用平台V5.0版本发行说明 机器视觉通用平台V5.0版本发行说明地址
了解更多
System.Windows.Controls 命名空间 | Microsoft Learn
控件库 - WPF .NET Framework | Microsoft Learn
WPF 介绍 | Microsoft Learn
使用 Visual Studio 创建新应用教程 - WPF .NET | Microsoft Learn
https://github.co… · 2026/9/25 18:25:52
只用一个问题训练几百步,模型居然还在变强:一篇论文的意外发现 先说一件让人有点摸不着头脑的事。有研究团队拿出一个数学题,就一道题,反复喂给模型训练了上千步。按常理这事儿应该很快就练废了,一道题能有多少信息量?可结果是,模型的准确率一路涨,涨到接近用全部一万七千道题训练出来的效果的七成二。这不是巧合,也… · 2026/9/25 18:25:34
Atlas 300V 24G实战:从NPU选型到YOLO生产级部署 刚拿到Atlas 300V 24G这块卡的时候,我第一反应也是——这不就是一块显存比较大的“图像处理卡”吗?直到把YOLO模型完整跑完一遍,才真正搞明白它和普通GPU加速卡的区别。这篇文章不整虚的,就围绕两个实际问题展开:Atlas… · 2026/9/25 18:25:34
小模型能当裁判吗?一场关于强化学习奖励成本的实验 先问你一个问题。如果你要训练一个AI模型写深度研究报告,怎么判断它写得好不好?数学题有标准答案,代码题能跑测试用例,可一篇论文该不该给9分还是7分,谁说了算?过去几年,大模型圈子里流行的做法… · 2026/9/25 18:25:27
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37