做数据库迁移这些年,我最怕听到的就是“这表是分区表,顺带把子分区也过去一下”。普通表全量一灌、增量一追就完事,但分区表,尤其带二级子分区的组合分区表,迁移起来牵扯的DDL转换、数据路由、分区定义保真、后续增量里的分区维护操作,随便一个环节掉链子,迁过去的分区表就是一张披着分区外衣的普通大表,分区裁剪失效、分区运维做不了,业务高峰期SQL一跑就是全表扫描。
这篇文章写的是我用达梦的DMDRS(达梦数据复制软件)迁移分区表子分区功能的完整经验,从底层机制到实操配置,再到一路踩过来的坑,按真实项目里的推进顺序讲,给同样要做异构库向达梦迁移、或者达梦之间做分区表同步的人一个能直接参考的底稿。
1. 迁移分区表和子分区,为什么不能当普通表处理
1.1 分区表迁移的“定义保真”问题
普通表迁移只需要保证三件事:表结构正确、数据量一致、索引约束齐全。分区表迁移多了一个硬指标——分区定义必须和源库等价。这里用“等价”而不是“完全一样”,是因为异构数据库之间的分区语法本来就不可能逐字符一致,但分区键、分区类型、分区边界、子分区层级这些决定语义的要素一个都不能丢。
实际迁移中,分区定义丢失一般发生在两个阶段。一是建表阶段直接用了工具默认生成的DDL,有些迁移工具不解析源库的分区语句,默认按普通表建目标表;二是全量数据灌完之后,手动补分区脚本时边界值写错,例如源库范围分区最后一个分区用了MAXVALUE,目标库写成了具体日期,业务数据一旦超过这个日期就直接报ORA-14400类似的错误(达梦对应报错是分区键未找到匹配分区)。DMDRS在元数据解析这块做得比较完整,源端表结构里带的分区、子分区定义会被解析成统一的中间模型,再按目标库语法生成DDL,从机制上避免了第一个坑。
1.2 子分区为什么更“娇贵”
子分区就是二级分区,Oracle里叫组合分区,支持RANGE-HASH、RANGE-LIST、LIST-HASH、LIST-LIST、HASH-HASH这些组合。达梦DM8同样支持组合分区,语法和Oracle高度兼容,但细微差别不少。
我迁过一个订单明细表,源库Oracle里的定义是RANGE分区按订单日期、HASH子分区按客户ID,一级分区5个、每个下面4个HASH子分区,总共20个子分区。这种结构迁移时最大的难点不在DDL生成,而在数据装载阶段:工具必须识别数据属于哪个一级分区、再路由到哪个子分区,两层判断错一层,数据就进错分区。普通迁移工具经常只做一级分区路由,子分区要么不支持、要么所有数据直接落进默认子分区。DMDRS迁移任务里能够按完整的二级分区结构去匹配分区键,这是它处理这类场景的核心能力。
1.3 DMDRS在整个链路中的定位
DMDRS在达梦生态里的角色,说白了就是数据搬运工加同步员。源端支持常见主流数据库,目标端是达梦,能做一次性全量迁移,也能做基于日志解析的增量同步,还能先全量后增量追平。分区表子分区迁移只是它能力范围里的一块,但也是异构迁移里最能看出工具功力的场景。适合用这套方案的场景很典型:国产数据库替代改造、数据中台多源异构数据上收、达梦作为备库或分析库的实时同步。这类项目有个共同点——不允许长时间停机,分区表体量大、业务还在跑,必须全量加增量平滑切换。
2. DMDRS处理分区表子分区的核心机制
2.1 元数据解析和DDL转换
DMDRS拉取源端表定义时,会读取系统目录视图里的分区信息。以Oracle为例,对应的是DBA_TAB_PARTITIONS、DBA_TAB_SUBPARTITIONS、DBA_PART_TABLES这几个视图,拿到分区类型、分区键列、边界值表达式、子分区模板、各级分区名列表。拿到这些原始信息后,工具内部会构建一份与数据库厂商无关的分区模型,再套用达梦的DDL生成器输出建表语句。
这个中间模型很关键。因为直接做字符串替换式转换,Oracle的SUBPARTITION TEMPLATE到达梦就得改写成达梦的模板写法,Oracle的INTERVAL间隔分区到达梦如果版本支持则可以映射、不支持就得展开成显式分区。在我实际使用中,达梦兼容Oracle模式下,RANGE和LIST分区转换基本能达到一比一还原,HASH子分区的模板也能保留。但有一点必须人工复核:源库如果用了TO_DATE('2024-04-01','YYYY-MM-DD')这种带格式字符串的边界值,生成的DDL里可能带上会话相关的NLS设置依赖,稳妥的做法是迁移前在源库把分区边界值取出来,人工确认一遍再放行。
2.2 数据装载阶段的分区路由
DDL建好了,数据怎么进到正确的子分区,这是另一道关。DMDRS做全量迁移时,是按分区为粒度规划和执行读取任务的,每个分区(或子分区)的读取任务可以并发跑。数据的INSERT语句不需要手动指定分区名,而是靠分区键的值让数据库自动路由。但大理路容易在小路上翻车——RANGE分区对边界值敏感,VARCHAR2类型的分区键如果源库和目标库字符集排序规则不同,同一个字符串可能落进不同的分区。
HASH子分区更要特别注意:Oracle的哈希算法和达梦的哈希算法不是同一套。同一条数据在Oracle里按CUSTOMER_ID哈希后进了子分区SP_03,在达梦里可能进SP_01。这不是迁移错误,数据总量没变、路由到的一级分区没变,只是HASH子分区的物理分布和源库不一致。如果业务侧有直接指定子分区名的SQL,或者有按子分区做数据归档的脚本,迁移前一定要改掉这类写法,否则后患无穷。我后面在踩坑部分会详细展开这个点。
2.3 增量同步阶段的分区维护操作
全量迁移完只是起点,生产环境的分区表每天都在变,不是数据变,而是结构也在变——月底加分区、定期清历史分区、临时按季度合并分区。增量同步阶段,DMDRS通过日志解析捕获DML变更,同时也能识别源库的DDL操作。加分区、删分区、TRUNCATE分区这类常规维护操作,工具能转换成目标库语法重放。
但SPLIT分区、MERGE分区、EXCHANGE PARTITION这类复杂操作,不同数据库差异太大,而且涉及数据在分区间的物理移动,自动化同步的风险很高。我的经验是,增量任务运行期间如果源库要做这类操作,先和业务确认能不能避开同步窗口,避不开就暂停增量任务、在目标库手工执行等价DDL、再恢复任务。日志解析型同步工具都建议开启DDL同步开关,但开了开关不等于所有DDL都能安全重放,这个认知一定要有。
3. 实操:把一张带HASH子分区的订单表从Oracle迁到DM8
3.1 迁移前摸底和准备工作
我实际操作的表来自某业务系统的订单明细,源库Oracle 11g,目标库是达梦DM8,字符集都是UTF-8,源表ORDER_DETAIL有2亿行、大小约80GB,组合分区结构是RANGE按ORDER_DATE季度分区加HASH按CUSTOMER_ID四子分区。动工具之前,我先在源库跑了一轮元数据采集,确认分区结构是否完整。
-- 源库确认一级分区 SELECT table_name, partition_name, subpartition_count, high_value FROM dba_tab_partitions WHERE table_name = 'ORDER_DETAIL' ORDER BY partition_position; -- 源库确认子分区 SELECT partition_name, subpartition_name FROM dba_tab_subpartitions WHERE table_name = 'ORDER_DETAIL' ORDER BY partition_name, subpartition_position;这一步不能省。分区表迁移里大量的失败案例都源于源库分区定义本身已经“不干净”,比如存在空的中间分区、分区边界值重叠、或者子分区模板和实际子分区不一致。先摸清家底,再看工具能还原到什么程度。同时要规划目标库的表空间,源库分区如果分散在多个表空间,目标库要提前建好对应表空间并做好表空间映射,否则多个分区挤到默认表空间,迁移任务跑到一半磁盘满了才被动。
3.2 创建迁移任务并配置关键参数
DMDRS的图形管理界面里,创建迁移任务的流程不复杂:配置源端连接和达梦目标端连接,选择迁移类型为“全量+增量”,对象选择里勾选ORDER_DETAIL表,映射关系确认无误,然后进入参数配置页。
这个页面里的参数直接决定迁移能不能在窗口内跑完。以我当时的环境(源端和目标端之间千兆网络,目标库服务器16核CPU)为例,参数我这样配:
| 参数项 | 推荐配置 | 配置思路 |
|---|---|---|
| 全量读取线程数 | 6 | 源库是生产库,线程开太高会把源库IO打满,影响在线业务。6是个兼顾速度压力的值 |
| 装载线程数 | 8 | 目标端写入能力比源端读取更容易成为瓶颈,稍微多给一点 |
| 批量读取行数 | 5000 | 每次从源库FETCH的行数,太小交互次数多,太大内存容易吃紧 |
| 提交行数 | 50000 | 攒够5万行提交一次事务,减少提交频率,提高装载吞吐 |
| 增量同步延迟阈值 | 5秒 | 增量阶段日志解析延迟超过5秒触发告警,便于及时介入 |
| 错误容忍度 | 严格模式 | 遇到数据错误立即暂停任务,避免错误被跳过导致数据不一致 |
配完这些,还有两个容易忽略的开关。一个是“迁移完成后校验”,建议打开,工具会做行数级和关键字段级的比对;另一个是“DDL同步开关”,全量阶段不需要,增量阶段必须开。我当时先做了一次空跑,选了源库里一张只有几千行的普通表跑通全流程,确认连接和权限没问题,再正式跑大表。
3.3 全量迁移执行和推进节奏
全量任务启动后,我做的事不是干等,而是一直盯着两个地方:目标端表的创建进度和源库的负载。DMDRS会先建目标表结构和索引,再开始导数据。目标表是带20个子分区的组合分区表,建表SQL在达梦里执行得很快,几十秒就完成。建完表后我立刻查了一遍目标库的分区元数据,确认一级分区5个、每个下面4个子分区,总数对得上,才开始放心看数据装载。
数据装载期间,源库的IO等待和CPU占用比平时高了30%左右,这是6个读取线程的正常代价。网络传输上,工具默认不做压缩,80GB的数据按千兆网跑,理论时间差不多12分钟,但加上类型转换、SQL组装、目标端写盘,实际用了40分钟左右。执行过程中目标端出现了一个小报错——有一批数据的目标端插入超时。排查下来是源端一批数据里混入了异常长的VARCHAR字段值,目标端写入时触发了行长度限制。处理办法是临时调大了装载线程的提交行数间隔,同时把那批异常数据单独过滤出来人工核验。这里也体现了一个好习惯:跑大任务前,先确认源表哪些列可能存在超长值或空字符串陷阱。
全量结束后做了一次行数对比,源库2亿行,目标库也是2亿行,一级分区各自的行数我用下面的SQL核对,基本一致。这是全量迁移阶段最核心的验收动作。
-- 目标库:按一级分区统计行数,和源库同SQL结果对比 SELECT partition_name, COUNT(*) AS row_count FROM ORDER_DETAIL PARTITION (P_2024_Q1) UNION ALL SELECT partition_name, COUNT(*) AS row_count FROM ORDER_DETAIL PARTITION (P_2024_Q2) UNION ALL SELECT partition_name, COUNT(*) AS row_count FROM ORDER_DETAIL PARTITION (P_2024_Q3) UNION ALL SELECT partition_name, COUNT(*) AS row_count FROM ORDER_DETAIL PARTITION (P_2024_Q4);3.4 增量追平和业务切换
全量完成时,源库还在持续写入,此时目标库只有全量那个时间点的数据。增量任务从全量启动时刻的日志位点开始解析,不断把新发生的INSERT、UPDATE、DELETE同步过来。我观察了一段时间,同步延迟稳定在2到3秒,说明日志解析和重放能力能跟上源库的业务写入速度。
追平到延迟足够低之后,和业务方约了一个切换窗口:应用停写、增量任务把剩余的变更全部同步到位、延迟归零、应用连接切到达梦。切换窗口总共15分钟,其中真正等增量追平只用了不到1分钟。切换后我让应用在达梦侧做了一轮核心链路冒烟,重点看那些走分区裁剪的SQL执行计划有没有变化。
4. 踩坑实录:常见问题与排查技巧
4.1 边界值写法不兼容导致建表失败
DM8对Oracle的兼容性很好,但有一个细节坑了我一次。源库的RANGE分区边界值写的是TO_DATE('2024-07-01','YYYY-MM-DD'),DMDRS转到达梦时保留了这条表达式,建表时报错。原因是这台达梦库的兼容参数设置里,TO_DATE的格式串匹配行为和Oracle有差异,日期格式串被当成普通字符串比较。处理办法是把建表SQL里的边界值改成达梦更稳妥的日期字面量写法,比如TO_DATE('2024-07-01 00:00:00','YYYY-MM-DD HH24:MI:SS'),或者直接改成达梦的'2024-07-01'隐式转换写法,重新执行。
这类问题属于“一眼看不出毛病、跑起来就报错”的类型,排查思路是看工具生成的DDL,逐条边界值核对。我的习惯是迁移前把工具生成的目标端DDL先导出来人工过一遍,特别是分区边界值,比等建表失败再补救省时间得多。
4.2 字符集差异导致字符串分区键路由错位
源库如果是ZHS16GBK字符集,目标达梦是UTF-8,同一个中文字符串在GBK里的字节序和UTF-8里完全不同。RANGE或LIST分区键如果用的是中文业务编码,迁移过程中工具做类型转换后,分区边界值的大小比较可能会错位。我遇到过LIST分区表迁移后,某几个分区里混进了不该在这个分区的数据,源头就是字符集转换后分区键的字符串排序发生变化。
排查方法很直接:迁移完成后,把每个LIST分区的边界值对应的键值单独抽出来,在源库和目标库分别执行“按分区统计+边界值上下界的数据量”对比。如果错位,工具层面很难自动修正,需要人工重建分区并重灌受影响分区的数据。这类问题的根治办法是前期规划阶段就把源库到目标库的字符集转换规则定好,在DMDRS的连接配置里明确字符集映射,不要依赖默认值。
4.3 HASH子分区行数和源库对不上,但这不是错误
这是最容易引起恐慌的一个现象。我在校验时发现,目标库20个子分区里,有好几个子分区的行数和源库同名子分区对不上,当时第一反应是路由写错了,查了半天没发现问题。后来才确认,HASH分区使用的散列算法在不同数据库厂商之间没有标准化,Oracle的HASH分布和达梦的HASH分布天然不同,同样的数据集合在两边按子分区统计,行数必然有差异。
判断这类情况是否正常的标准很简单:看全表总行数是否一致,再看每个一级分区下所有子分区的行数之和是否一致。只要这两层对得上,HASH子分区内部的分布差异就不算数据问题。但要注意,如果应用层有按子分区名做数据清理或归档的定时任务,这种分布差异会导致“某个子分区预期有多少行”的假设失效。我那次项目的处理办法是调整了归档脚本,改成先按一级分区筛选、再按业务字段过滤,彻底去掉对子分区行数的依赖。
4.4 分区数量超限和表空间映射异常
达梦的分区数量上限和数据库页大小有关。源库如果是个分区非常细的表(比如按天分区存了5年,1800多个分区,再加上子分区),目标库建库时页大小若是8K,创建分区表时可能直接报“分区数超出限制”。我之前接过一个环境,源库表分区总数量过了4000,目标库用16K页大小建库才顺利通过。所以目标库的init参数在实施前就要想清楚,尤其是TP类系统做分析型报表库这种,分区数量一定比普通系统多,页大小别设小了。
表空间映射问题则出现在源库每个分区指定了独立表空间,而目标库没建对应表空间的时候。DMDRS任务会默认把目标表所有分区放默认表空间,跑起来之后默认表空间疯涨。这个在3.1里提过,实操中一定要提前把表空间对应关系列成一张映射表,工具支持按表空间映射就配映射,不支持就迁移后手动把分区挪到规划表空间。
4.5 增量阶段SPLIT/MERGE分区导致任务中断
增量同步跑得正稳的时候,源库DBA为了调整分区粒度,执行了一次SPLIT PARTITION,把一个季度分区拆成3个月度分区。DMDRS的日志解析识别到这条DDL后,在目标端重放时直接报错,任务暂停。原因是达梦和Oracle对SPLIT分区边界值的处理语法有差异,工具自动转换没成功。
处理流程是这样的:先暂停增量任务,记录当前同步位点;然后在目标库手工把源库那次的SPLIT结果等价执行一遍;再把增量任务从暂停时的位点恢复。恢复后,源库后续的数据变更就能正常续上,因为分区结构两侧已经一致。这件事之后我养成了个习惯:每次增量任务启动前,都和源库DBA对一遍近期分区维护计划,凡是涉及SPLIT、MERGE、EXCHANGE这类操作的时间窗口,要么错开,要么提前安排好人工介入预案。
4.6 源库归档日志不足拖垮增量追平
全量迁移用了40分钟,这期间源库的归档日志一直在产生。增量任务启动追平时,需要从全量启动时刻的日志位点开始读,如果源库的归档日志保留时间不够,或者归档目录空间被其他任务挤占,DMDRS会报日志缺失错误。这个错误比数据错误更麻烦,因为缺了那一段日志,中间的数据变更无法补齐,只能重新做全量,工作量翻倍。
避免办法是在全量迁移启动前就和源库管理员确认归档保留策略,确保至少保留到“全量+增量追平+业务切换”全部完成之后。如果归档空间紧张,把迁移窗口放在业务低峰期,全量时间也能缩短。这是整个迁移任务里最需要跨团队协同的一环,纯靠工具解决不了。
5. 复盘与建议
5.1 迁移策略这么选最稳妥
分区表迁移到底用全量离线、还是全量+增量,主要看两个约束:停机窗口和数据量。表在百GB以内、允许停机几小时,直接全量离线最简单,校验做完就切换,没有增量阶段那些日志解析的麻烦。但我接触的项目里,订单、流水这类核心业务表几乎都要求不停机,那就只能全量+增量追平。跑这套路的流程我总结下来就是四步:全量灌底、增量追平、应用停写、切换校验。每一步的完成标准都要量化,比如全量校验行数一致、增量延迟小于5秒、切换后冒烟SQL执行计划无退化。
5.2 几个贯穿全程的好习惯
第一,迁移前建议把源库的目标表DDL、分区边界值、索引定义、触发器、依赖视图全部导出归档,这些脚本既是工具建目标表的参照,也是事后排查的依据。第二,小表先行,正式大表迁移前,用同库一张结构类似的小分区表跑通全流程,界面上那些参数先验证一遍,别拿2亿行的表试错。第三,DMDRS任务如果支持断点续传,全量阶段开启,占不了多少额外资源,但一旦网络抖动或目标端磁盘满,不用从头再来。第四,迁移期间每天记录一次同步位点、延迟和错误日志,出问题的时候能快速定位是哪个时间点开始偏的。
最后再分享一点个人体会
分区表子分区迁移这件事,工具能解决的占八成,剩下两成考验的是人对分区语义的理解。DMDRS把DDL转换、数据路由和增量重放做得已经很省心,但边界值兼容、字符集影响、HASH分布差异这些坑,工具不会替你想。我在做这类迁移时,不管工具的校验报告多漂亮,都会坚持拉一遍目标库的真实执行计划,尤其那些依赖分区裁剪的SQL,再让应用团队做一轮接近生产的压测。分区表迁移的终点不是数据对上了,而是业务在目标库跑得和源库一样稳,甚至更快。这个底线抓到手里,迁移才算真正落地。