当前位置: 首页 > 科技观察

如何规避异构数据库迁移的九大坑

时间:2023-03-14 21:04:51 科技观察

“二错”就是重复同一个错,句子是《论语》,完整的句子是“别出气,别犯二错””,奈仲尼已经表达了对已故弟子颜回的不幸的高度赞扬。就IT项目而言,站在管理者的角度,一个错误犯两次是不能接受的,更何况是同一个人,哪怕是同一个团队的不同人。说到这里,我不禁想象一个平时和蔼可亲的老领导,面露怒火,大喊“杀人,杀人”的恐怖场面。不犯同样的错误真的很难吗?说实话,真的很难。难道你不知道,人类从历史中学到的最大教训就是永远不要从中吸取教训。作者的上述感叹背后还有一个故事。那是本世纪最“双”的一天。笔者在调研Oracle与ADB(这里指的是ADBPG,全称AnalyticDBPostgreSQL版,是阿里MPP架构的数据库)之间的实时数据同步过程,突然发现埋了这么一大块一——不区分空值和空字符串,导致同一个字段值在全同步阶段被标记为空字符串,在增量同步阶段被标记为空值,这显然不是逻辑行为.其实在发现这个问题之前,笔者已经为这个项目折腾了一个月。前两天笔者认为数据质量完全没问题,数据同步达到了“notunusable”的状态(作者注:这里的usable是真的可用,不是上线用的那种)。OracleDB本身不区分空值和空字符串。甲骨文的工程师没有意识到这一点似乎是可以理解的。但是,笔者作为搬砖人,十几年来回搬家,数据库也是异构的。我也认真做过几个迁移项目。对于笔者来说,这件事其实无异于一个“低级错误”。现在回想起来,这个项目的作者很尴尬。虽然有时间紧迫和新工具新技术的引入需要边使用边学习等客观因素,但这并不足以平息笔者对自己的怒火。问题来了。在开源技术使用越来越多的今天,可能突然需要作者使用新的工具同步数据到新的数据库,时间可能会更紧迫。那怎么办呢?你又要生气了吗?不行,还是脚踏实地总结一下,把这些坑记下来。以后类似的项目,就算是拿枪指着脑袋,前期也要考虑以下几个问题。1.空值和空字符串对于OracleDB来说,空值和空字符串是一回事。判断一个值是否为空值/空字符串,统一使用“ISNULL”。As=''无效。对于其他数据库则不一定如此。对于源为OracleDB,目标为其他数据库的项目,我们需要与开发人员确认目标使用哪个值来表示源处的空值/空字符。对于实时同步项目,在全量同步和增量同步时也需要保持数据的一致性。除了空值和空字符串,其实还有一个隐藏的大boss——定长类型中的空格值。以OracleDB为例,char类型用于存储定长字符串,长度不足的字符串会自动用空格补齐。对于这样的值,无论是char_col=''还是char_colisnull都无法识别,只能通过trim(char_col)isnull来识别。对该值使用长度函数将返回该字段的长度,一个非零值。但是这个数据同步到adb之后,表现就不一样了。char_col=''可以识别这个数据(即使adb也使用定长数据类型),但是对这个值使用char_length函数,结果会返回0。如果我们真的要抓取这种数据,我们有添加条件oct_length(char_col)>0。很明显,这是一个陷阱!2.数据类型转换问题笔者的朋友阿强一直认定自己是一个斯文儒雅的人。嗯,前提是不让他“写材料”、“擦屁股”、“估工”。数量”!没错,“预估工作量”对于他这种自称txt狂魔的人来说是一种折磨。刚说到这个词,他就已经幻想着把pm按在地上,边打边说,“我要做好人,你为什么要逼我”?问题来了,如果让阿强估算一下这个数据类型转换的工作量,估计连40米的刀都能拿出来。表字段数据类型的跨数据库转换主要涉及“精度”、“效率”、“兼容性”等方面。有些数据同步软件确实有自动转换的功能。但是,这种情况只适用于管理规范的数据库,非规范的数据库则需要付出很大的努力。下面举几个典型的例子来说明这个问题:从Oracle以DB到PG的同步为例。当然,整数值也可以通过number类型存储在源Oracle数据库中(不设置精度和小数位)。问题来了,PG中应该使用哪个字段,只是为了安全起见,为了不失精度,必须是数字类型,但是毫无疑问会有性能损失。Bigint等类型自然是更好的选择,但是这也涉及到与开发人员的沟通,可能会导致应用程序出错等,这又是一个工作量。以从OracleDB同步到DB2为例,DB2有一个隐含的要求,组成主键的字段值前后不能包含空格,否则会被过滤掉,这样会造成一些二源Oracle数据库中的不同记录。目标DB2库中的记录被误判为同一条记录,导致数据冲突,影响数据同步。不要问为什么在id类字段中放置空格:这是合法的;这是一种备份数据的创新方式。这样做没有错,真的!以从OracleDB同步到ADB为例,由于MPP架构的要求,我们需要额外指定DISTRIBUTED列。对于ADB,还有一个额外的要求,即此列应该是主键的一部分。以从OracleDB同步到HBase为例。HBase是强制要求有主键的,否则无法同步。在之前的项目中,笔者被迫使用Oracle转换后的ROWID作为HBase的rowkey来满足其同步前置条件。为什么是转换后?这是另外一回事了,这里就不展开了,只提示一个关键词,“预分区”。嗯,这明显是个坑。真的很感谢这个表结构由开发者决定的项目,真的!同步,下同)在迁移项目中,涉及到BI??G5到UTF8的转换,当时的同步工具是OGG。这个项目中各种乱七八糟的数据,笔者至今还历历在目。当然,这也让笔者在处理异构数据库同步中的字符集转换问题时更有经验。跨字符集转换的工作其实有很多坑。因此,如果条件允许,笔者建议尽量避免。当然,如果PG的服务器端不支持GBK,而源OracleDB是GBK,那我们就得迎难而上了。以下是笔者的建议:中文的情况,所见非所得,判断一个中文记录是否正常的依据应该是十六进制编码是否正常。对于OracleDB,我们可以使用dump功能,其他DB需要找类似的。中文是多字节字符,不同字符集占用的字节数不一致。典型的例子是GBK为2个字节,UTF8为3个字节。目标环境可能需要相应地调整字节宽度。不同的字符集所涵盖的汉字是不同的。例如,BIG5中没有“村庄”。字符集中有一个“自定义”区域,如果不进行特殊处理,可能会导致数据丢失。“乱码”会带来很多麻烦。对于OracleDB,大致有以下几种情况:无法通过Oracle自带的convert函数转换为目标库代码。以ADB为例,这部分数据会导致GPFDIST导入失败。这会影响全量数据同步。可以通过Oracle内置的convert函数转换为目标库代码,但不能重新转换为原始数据。这部分数据会有潜在的数据丢失影响。对于迁移项目,需要重点关注是否涉及到“自定义”字符区域。意义不明确的单字节字符,如chr(0)、chr(255),这些字符往往混杂在正常字符中。以ADB为例,涉及到dts(DataTransmissionService,数据传输服务,是阿里的数据同步工具/服务)增量同步,对应的记录存在数据一致性风险。4、特殊字符处理对于异构数据库同步,单引号、双引号、换行符、斜杠、反斜杠等特殊字符也是比较麻烦的项目,尤其是在全量数据同步阶段。对于文本模式下的全量数据同步,我们可以考虑以下方法:使用CSV格式;使用多字节定界符;执行数据清理;只同步“正常”数据,“特殊”数据另行处理。5、异常记录处理这里的异常记录是指本身就违反了数据库规范,不应该被插入到数据库中的记录。以OracleDB为例,笔者遇到的记录包括异常的日期格式和异常的值。日期格式异常,典型例子有“0000-00-0000:00:00”和“2022-02-3000:00:00”。笔者在几个客户环境中都遇到过这种数据,以至于笔者认为很“常见”,需要加入到测试项目中。这段时间做的Oracle到ADB同步项目确实遇到过这种数据,后面还导致dts的增量同步中断,风险很大。好在笔者的dts源库是基于OGG目标库部署的,而Oracle自带的OGG工具无法同步这类数据,间接屏蔽了这部分异常增量数据。在此基础上,笔者只需要对存在的异常数据进行修复即可。修复方法也很简单。先+1再-1可以修复大部分数据。异常值类型,典型的例子是NaN(NotaNumber),笔者只在一个客户环境中遇到过,当时的场景是O2O同步。更可怕的是,连最基本的“全能”数据泵都同步不了这种数据。考虑到在这个环境下从来没有遇到过这种数据,所以笔者这次偷懒,没有做相应的测试。6、全量同步测试一般情况下,各种数据同步软件都会有全量数据同步的功能。至于这个功能的效率、资源消耗、空间占用等项目,需要进行评估。如果不能满足需求,则可能需要考虑替代方法。在选择测试表时,笔者考虑了以下几个因素,选择了几个测试表:需要包含大表,往往是瓶颈项;需要包括本次同步涉及的表的字段类型;字节数据,建议包含这样的表;建议找个静态表或者准静态测试,方便检查数据一致性。7、增量同步测试作为数据同步项目,同步效率是一个重要的因素。笔者建议在构建完整的同步链路之前,对数据变化频繁的关键表进行测试,通过单表流程消除潜在的配置。不适当的风险。对于这方面,笔者有以下建议:尽量使用真实数据;作者在本次测试中使用Ogg同步增量数据,更适合生产中的实际变化,可以参考此方法。发起增量同步后,在目标数据库后台观察相应的SQL语句。以笔者的项目为例,这个阶段发现了两个问题:由于区分大小写,导致dts目标端无法识别主键,导致所有字段都加入到where条件中,影响效率。这个问题后来通过修改同步配置解决了。笔者观察,虽然在dts端设置了高并发度,但在实际运行中,只有少数几个进程在工作,其余都是闲置的,无法充分利用资源。后来通过修改配置参数解决了这个问题。8.数据一致性测试数据一致性是另外一个话题,可以另写一篇。为此,作者提出以下建议:比较静态或准静态数据。很显然,笔者这次使用的ogg中间库方案非常适合这个主题。如果没有这个东西,笔者只能在停止同步过程后查看是在哪个点停止,然后用这个时间点进行检查。这个思路理论上是可行的,但是根据笔者对dts粗浅的了解,这条路是行不通的,因为dts停止的时间点并不完全准确。使用md5函数。大多数正经的数据库都内置了一个md5函数(PS:DB2是无意的内涵,真的不是有意的),可以将一个复杂的字符串简化,从而可以用来进行两端数据一致性确认的操作。9、软件限制《女人越漂亮越会骗人,切记!产品售前售后的结果——同一个目标,走不同的路。售前,拼指标、造场景、讲故事等手段,都是为了证明我们的产品很棒,快来抢购;售后方面,找到产品痛点,规避痛点,确保工作顺利,达到避免天上掉下大锅的目的。每个人都是玩游戏出生的,没什么不同,手里的牌都是对技术的理解。扯远了,回到it项目,异构数据库的同步往往是一种逻辑同步的方式,必然存在各种瓶颈。对于售后服务,再怎么辱骂售前也于事无补。最现实的做法是找到短板,通过改进流程、优化需求,甚至与开发者合作修改应用,来保证软件的稳定运行。这里先讲一个故事。Timesten是Oracle的内存数据库。它的Cachegroup功能可以实现从物理数据库(即OracleDB)到内存数据库的实时数据同步,而这种同步延迟对于业务的稳定运行是非常关键的。在实际使用中,运维人员总结的经验是为了避免事务发生重大变化。最终他们和开发者达成了相应的操作规范,无论是业务变更需求,还是数据库运维发起的清洗操作,比如涉及Timestens同步表,当变更量达到10万条时,必须分批提交,2万条每批记录和批次之间30秒的睡眠。我觉得这个故事的结局很圆满。真的,如果非要死死抱着Timesten不放,打算纯粹靠软件解决问题,那就妥妥的灾难现场了。毕竟它基于触发器的同步机制从原理上来说对大事务是极其不友好的……问题是,如何找到软件的不足之处呢?阅读官方文档自然是一个渠道。当然,看书也是有“技巧”的:我们支持xx索引以内的场景,这句话可以理解为超过这个值就得想办法了。同时,这个价值可能要打个折扣。毕竟环境不同,有区别也是很合理的。我们支持功能a,也支持功能b。这是真实的。至于同时支持功能a和b,那是你自己的看法,我没说。嗯,有春秋笔画,这种玩法自古就有。陈寿在《三国志》里不是也说清楚了司马昭弑君吗?后来大家不是都知道了吗?除了文档之外,我们还可以根据自己的经验考虑以下几点。大事务测试对同步范围内外的对象进行批量操作,增加数据库日志量,观察其对数据同步和系统的影响,具体包括cpu、内存、io、空间等资源消耗,以及同步延迟。以dts为例,源头oracle数据库产生的所有数据都会被拉入dts数据库进行分析,即使这些数据与我们的同步策略无关。目前有一个黑名单功能可以绕过这个问题。长事务测试包括增量同步前开启的事务是否能正常同步,长时间未提交的事务是否影响同步进程的重启等维度。很明显,这是被ogg吓到的结果。频繁交易测试笔者曾经遇到过一个在O2O同步环境下大量使用withas语法的应用。后者隐式开启了大量空头交易,随后交易量在短时间内暴涨,导致同步软件Ogg提取过程出现延迟。通过要求开发人员修改语句解决了这个问题。不过它对笔者的心理阴影一直都在,以至于每次遇到新的场景都会思考自己会不会遇到类似的问题。事务的顺序是探索软件同步是否可以维护事务的顺序。如果不能保养,那就要多加注意了。这种情况会导致误判同步延迟,导致旧数据覆盖新数据,影响数据。一致性。一个简单的测试例子是创建一个周期性(例如每分钟)更新的时间戳表,并且该表中的记录数与源OracleDB中的节点数一致。计时脚本依次连接每个实例,并用当前时间更新相应的字段。在目标端,我们可以通过查询时间点表观察是否会出现以下两种情况:是否会先查询更新的数据;synchronizationdelay如果是,时间戳表中记录的时间是否与同步过程的时间戳一致。批量DDL测试对于基于数据库日志的同步工具,大批量的DDL语句很可能在源端触发慢解析。毕竟,这涉及到与数据字典的交互。笔者曾经在基于Ogg的O2O同步环境中遇到过这种瓶颈,最终的解决方案是调整开发者版上传的脚本,增加DDL语句的等待时间间隔。基于对同步原理的短板探索,基于rowid的同步方案无话可说。我们只能从规范上减少move、shrink等改变rowid的操作。如果确实需要操作,需要重新同步相应的数据。对于基于主键的同步方案,重点关注如何处理没有主键的表。进程重启测试包括正常重启和非正常重启两种场景。异常重启是指高可用方面,这里不再详细展开;至于正常重启,需要观察进程的一些自定义参数是否会被重置。