
1. 项目背景马来西亚客户的迁移诉求与选型逻辑1.1 客户现状一套跑了多年的 SQL Server 核心系统这个项目是帮一家马来西亚本地企业做数据库迁移源端是 SQL Server目标端是 OceanBase。客户的业务系统主要是内部的订单管理和库存核算数据库运行了挺多年数据量不算特别大单库在几百 GB 的量级但表数量不少有几百张其中一部分表还承担着高频的读写。客户最初的痛点很直白SQL Server 的授权费用越来越高而且他们原本部署的版本比较老很多新特性用不上。加上他们有几套系统要逐步整合希望在数据库层面引入一套能横向扩展、同时兼容 MySQL 生态的架构。OceanBase 是他们评估下来比较合适的选项兼容性做得不错又不像传统商业数据库那样有高昂的 license 成本。1.2 为什么选 SQLShift 作为迁移工具数据库迁移这件事工具选型往往决定了项目能少踩多少坑。当时我们也对比了几条路用官方自带的数据导出导入、用第三方 ETL 工具、或者直接用 SQLShift 这种专门的迁移工具。SQLShift 的核心优势在于它不只是搬运数据而是把 SQL Server 的 schema、存储过程、视图、触发器这些对象一并转换过来并且在转换过程中会做方言适配。这正好戳中了这个项目的痛点——客户的系统里有大量存储过程和视图如果用传统方式迁移光是重写这些对象就够喝一壶的。另一个原因是 SQLShift 支持全量加增量的迁移模式。对于客户这种不能长时间停机的业务系统先做全量基线再通过增量同步把切换期间的新数据追平最后在业务低峰期做割接整个过程的停机窗口可以压缩到分钟级别。1.3 迁移链路的整体设计整个迁移链路分成三段源端 SQL Server、迁移工具 SQLShift、目标端 OceanBase。SQLShift 在中间扮演的是翻译官和搬运工的双重角色。翻译官的职责是把 SQL Server 的 T-SQL 方言转换成 OceanBase 能识别的语法。搬运工的职责则是把表结构、数据、数据库对象完整地搬过去。这里面的细节远比想象中多——数据类型映射、索引处理、自增列转换、字符集归一化、时间类型精度对齐每一项单独拎出来都可能成为阻塞项目的问题。提示选迁移工具时一定要确认它支持的源端 SQL Server 版本范围。不同版本的 T-SQL 语法差异很大工具如果不熟悉某个特定版本的语法特性转换出来的目标端代码可能留有隐患。2. 迁移前评估SQL Server 版本差异与兼容性死角2.1 源库环境盘点从版本到排序规则拿到客户环境的第一件事不是急着动手迁移而是做一次彻底的源库体检。这个项目里客户的 SQL Server 版本是 2016但我们查看了一些系统表后发现里面有些库还保留着旧版本时代创建的对象这就要注意了。排序规则Collation是第一个要确认的点。SQL Server 默认的排序规则通常是 SQL_Latin1_General_CP1_CI_AS但这个客户在马来西亚系统里有一部分历史数据是用旧的字典序排序规则创建的。如果迁移到 OceanBase 后字符串比较和排序的语义发生了变化业务侧的查询结果就可能出现微妙的不一致。另一个需要关注的是 SQL Server 的兼容级别compatibility level。有些库可能还设置在 2012 甚至 2008 的兼容级别上这意味着某些 T-SQL 行为是按老版本规则执行的。迁移前最好把兼容级别统一提升到源实例支持的最新级别避免转换工具在解析时遇到旧语法。2.2 不同 SQL Server 版本的隐藏差异围绕 SQL Server 版本的问题我在这个项目里踩了不少坑有几个地方值得单独拿出来讲。临时表的处理方式不同SQL Server 2016 之前的版本和之后的版本对临时表在存储过程里的行为没有太大的规范差异但 SQLShift 在转换存储过程时对#temp和##temp的处理细致程度会直接影响转换成功率。如果工具版本不够新某些老式的临时表写法可能识别不了。分页语法的历史包袱老版本用ROW_NUMBER() OVER做分页新版本可以用OFFSET...FETCH。转换工具对这两种写法都需要支持。日期函数的差异GETDATE()、SYSDATETIME()、DATEADD这些函数在 OceanBase 里都有对应实现但精度和行为可能存在微差。特别是DATETIME类型在 SQL Server 里的精度是 3.33ms 的舍入规则而 OceanBase 的DATETIME默认精度是秒级如果不指定DATETIME(6)这种格式迁移后时间字段可能出现精度丢失。2.3 兼容性评估报告里最容易被忽略的三类对象在做兼容性评估时大家的注意力通常集中在表和存储过程上但实际迁移中最拖后腿的往往是另外三类对象。触发器SQL Server 里的触发器语法和 OceanBase 存在明显差异特别是INSERTED和DELETED临时表的引用方式完全不同。SQLShift 在转换触发器时需要把 SQL Server 的触发器语义映射到 OceanBase 的实现方式。如果源库里的触发器比较依赖递归触发转换后要格外小心。用户定义函数SQL Server 里的标量函数、表值函数在 OceanBase 中都有对应的写法但是多语句表值函数multi-statement TVF的转换难度比较大因为它的内部实现原理和 OceanBase 的语法结构存在差异。扩展属性与权限体系很多客户的数据库对象上挂着大量的扩展属性Extended Properties比如字段的业务描述、报表口径说明。SQLShift 未必会搬这些东西权限体系用户、角色、schema 所有权的映射也需要人工确认。注意在正式迁移前务必输出一份逐对象的兼容性评估报告并按照无需改动 / 工具自动转换 / 需要人工重写 / 不迁移四个等级分类。这份报告是后续排期的依据也是跟客户确认工作范围的凭据。3. SQLShift 部署链路与迁移任务配置3.1 部署网络架构跨地域迁移的延迟处理客户的源库在马来西亚OceanBase 集群当时部署在云上网络链路存在一定的跨境延迟。SQLShift 本身支持通过 JDBC 连接源端和目标端但高延迟链路上的数据迁移效率会受影响。我们采用的方案是在靠近源端的区域部署一台迁移执行机SQLShift 跑在这台机器上让它到源库的网络延迟保持在个位数毫秒级别。目标端 OceanBase 虽然距离远一些但因为 SQLShift 是批量写入单个 batch 的往返时间摊到大量行上影响不大。这里有一个配置细节SQLShift 的 JDBC 连接串里可以设置socketTimeout和connectTimeout在跨境网络环境下这两个参数务必设大一些否则长时间批量写入时一旦网络抖动连接可能被误判为超时。3.2 源端账号权限准备迁移工具连接源库时需要的权限比我们想象中要多。除了常规的SELECT权限还需要能够读取系统视图和动态管理视图这样才能获取表结构、索引信息、约束定义等元数据。具体来说源端账号建议至少具备以下权限VIEW SERVER STATE读取动态管理视图获取连接和事务信息VIEW DEFINITION读取对象定义用于转换存储过程、视图等SELECT和REFERENCES读取表数据和引用信息db_datareader和db_ddladmin如果工具需要生成变更脚本目标端 OceanBase 的账号则需要具备所迁移数据库的CREATE、INSERT、UPDATE、DELETE、ALTER等权限。如果涉及增量同步还需要目标端具备执行 DDL 的权限。3.3 迁移任务配置的核心参数SQLShift 的任务配置里有几个参数是决定迁移效率和稳定性的关键。批次大小batch size这个参数控制每次写入目标端的行数。默认值通常比较保守但在跨境链路上批次太小会导致网络往返次数过多吞吐量上不去。我们测试后把批次调到了 5000 行每批实测写入性能提升明显。并行度parallelism全量迁移阶段可以配置多线程并行读取源库。但要控制并行度太高了会打满源库的 IO 和 CPU影响业务。我们的经验是从 4 个并发开始观察源库的等待统计逐步加到 8 个。数据校验级别SQLShift 支持多种校验模式包括行数校验、checksum 校验、样本数据校验。全量阶段建议开启完整 checksum 校验增量阶段可以适当降低校验频率。4. 迁移实施结构转换、数据搬运与增量同步4.1 数据类型映射的对照与取舍SQL Server 和 OceanBase 的数据类型体系并非一一对应映射关系直接决定了数据落库后的表现。下面这个对照表是我们在这个项目里实际使用的映射方案。SQL ServerOceanBase说明INTINT完全对应BIGINTBIGINT完全对应DECIMAL(p,s)DECIMAL(p,s)完全对应MONEYDECIMAL(19,4)需要显式转换避免隐式精度损失DATETIMEDATETIME(6)提升精度避免舍入差异SMALLDATETIMEDATETIME(0)精度降为秒级NVARCHAR(n)VARCHAR(n) CHARACTER SET utf8mb4注意字符集转换NTEXTTEXT建议提前改造NTEXT 已废弃UNIQUEIDENTIFIERCHAR(36)业务侧注意比较方式BITTINYINT(1)语义一致VARCHAR(MAX)LONGTEXT大字段处理路径不同最需要注意的是NVARCHAR到VARCHAR的转换。SQL Server 里的NVARCHAR存储的是 UTF-16 编码而 OceanBase 的VARCHAR在 utf8mb4 字符集下存储的是变长多字节编码。如果源表里有表情符号这类 4 字节字符目标端字段长度必须预留足够空间否则写入时可能报超出长度的错误。在马来西亚这个客户的环境里源库的字符集相当不统一——订单表用了默认的 collation客户信息表却建成了 UTF-8 编码。这种混合状态在 SQL Server 里能正常运行但迁到 OceanBase 后字符集必须统一。我们在转换阶段就把所有字符字段统一为 utf8mb4并提前排查了 4 字节字符的存在情况。4.2 存储过程与视图的方言转换SQLShift 对存储过程的转换是整个项目里最有价值的部分。客户的系统中大概有 300 多个存储过程手工重写的话工作量巨大而工具的自动转换能覆盖大部分场景。但自动转换不等于完全不用管。以下几类存储过程在转换后大概率需要人工介入动态 SQL 拼接类如果存储过程里用了大量的EXEC(SELECT...)动态拼接转换工具很难正确处理因为字符串里的 SQL 片段需要依赖上下文语义分析工具只能做表面替换内部逻辑很可能漏改。依赖临时表结构的逻辑SQL Server 的存储过程里经常用SELECT ... INTO #temp创建临时表OceanBase 对临时表的语义支持有限这类代码需要改写。游标密集型逻辑SQL Server 的游标语法相对宽松OceanBase 的游标使用方式有更多限制。如果存储过程里大量使用游标逐行处理建议改写为基于集合的操作或者至少优化游标的使用方式。视图的转换相对简单一些但要注意SCHEMABINDING属性的处理。SQL Server 里带SCHEMABINDING的视图和基础表绑定OceanBase 没有完全相同的机制转换后直接取消这个属性即可但前提是应用侧不能依赖视图的绑定约束来保护表结构变更。4.3 大表迁移的任务拆解与断点续传这个项目里有几张订单明细表单表数据量过亿。虽然整体库不大但大表的迁移策略和普通表完全不同。我们按照订单日期对这几张大表做了范围分区分批搬运。SQLShift 支持基于 WHERE 条件的数据抽取利用这个特性我们把大表按照日期切成多个分片每个分片独立跑迁移任务。好处有两个一是单个任务失败后只需要重跑对应的分片不需要整表重来二是可以通过调整不同分片的并发度来控制源库压力业务高峰时段跑小分片低峰时段跑大分片。断点续传这个功能一定要确认清楚。SQLShift 在遇到网络中断或者目标端写入失败时能不能从断点继续而不是从头开始这个直接影响项目进度。我们在测试环境验证过手动 kill 任务后重新启动工具会从最后提交的事务之后继续抽取没有数据重复也没有丢失。4.4 增量同步从 CDC 到实时追平全量迁移完成后源库的业务并没有停新数据还在不断写入。这时候需要增量同步来把变化的数据追平到 OceanBase。增量同步的机制本质上依赖 SQL Server 的事务日志读取。SQLShift 在这方面的实现是模拟一个从库读取事务日志中的变更记录解析并应用而且支持 DDL 和 DML。这种方式的优势是对源库影响小不需要额外开启 CDC 或 Change Tracking。提示如果客户环境不允许读取事务日志某些安全策略会限制权限退而求其次的方案是使用 SQL Server 的 CDC 功能但需要在源库显式启用对库有一定侵入性。做技术方案时这两条路径都要提前确认可行性。增量同步阶段最怕的是源库出现大事务。比如某个批处理任务一次性更新了几百万行SQLShift 拉取这个大事务的日志并应用到目标端耗时可能比较长这段时间内延迟会拉大。解决思路是在业务侧配合把批处理任务拆成小批次或者在割接前把这类批量任务避开数据同步窗口。5. 数据校验、索引重建与性能调优5.1 行数校验与 Checksum 校验的双保险迁移完成后最紧张的环节就是数据校验。我们在这个项目里用了两个层面的校验相互印证。行数校验最简单直接每个表的源端行数和目标端行数对比。这个指标能在第一时间暴露出迁移遗漏但它有个盲区——如果某两张表的行数恰好一致但内容不对行数校验完全发现不了。比如一张表里少了一行、另一张表里多了一行两边行数还是相同的。所以还要做 Checksum 校验。标准做法是对每一行计算校验值再对整表聚合。但这里要注意MySQL 生态的CHECKSUM TABLE和 SQL Server 的BINARY_CHECKSUM算法不完全一样不能用同一个值跨库比对。更可靠的做法是选取关键字段通常是主键加所有业务字段在源端和目标端分别做排序后计算哈希再对比哈希值。SQLShift 内置的校验功能就是这么做的它会对每行数据计算一个基于字段内容的哈希值再按表聚合源端和目标端各自独立计算最后对比结果。5.2 统计信息收集与索引重建数据搬过去之后数据库对象虽然齐了但性能表现往往和新库的统计信息质量直接相关。这个环节如果偷懒割接后发现慢查询就非常被动。OceanBase 的优化器依赖统计信息来选择执行计划。数据刚迁入时统计信息要么没收集要么是复制源库的——这两种情况都不理想。正确的做法是在全量迁移完成后对目标库所有表执行一次完整的统计信息收集操作让优化器基于实际的数据分布重新做决策。索引的处理上SQLServer 的聚集索引在 OceanBase 里没有一一对应的概念。OceanBase 的主键就是索引的锚点如果你的源表建了聚集索引但主键是另一回事迁移后要重新审视主键设计。这个客户的项目里就有几张表源库用自增列做主键但还有单独的聚集索引转换后我们把主键和索引合并处理避免存储冗余。5.3 从慢 SQL 反查转换遗漏迁移完成后应用联调阶段暴露出的问题往往是慢 SQL。这些 SQL 在 SQL Server 上跑得很快到了 OceanBase 上却走了完全不同的执行计划。最典型的例子是查询加了NOLOCK提示。SQL Server 的NOLOCK表示脏读OceanBase 的语法不支持这个提示。SQLShift 转换时会直接过滤掉这类 hint但去掉之后如果原表上有锁等待OceanBase 的事务隔离机制会影响并发性能。这个只能靠业务语义分析来确认是否能接受。另一个反例是依赖隐式类型转换的查询。源库的字段是NVARCHAR查询参数传的是INTSQL Server 会自动做隐式转换在目标端字符集和数据类型变化后隐式转换可能导致索引失效。联调阶段要重点排查这类跨类型比较的 SQL。建议迁移联调阶段可以在 OceanBase 上开启慢查询日志把执行时间超过阈值比如 200ms的 SQL 全部捞出来逐个分析执行计划。重点看有没有不该出现的全表扫、有没有 join 顺序明显不合理的情况。6. 项目现场踩坑记录从 SSL 连接到驱动兼容6.1 SSL 加密连接导致的连接可靠性问题这次迁移过程中最让我们意外的一个坑出现在 SQL Server 连接层面。客户的数据库实例启用了强制 SSL 加密而 SQLShift 的源端连接驱动在解析证书时遇到了问题。错误信息大概是这样的驱动程序无法通过使用安全套接字层加密与 SQL Server 建立安全连接证书链由不受信任的颁发机构颁发。这类问题如果只用 JDBC 连接串常规配置会在 TLS 握手阶段失败。解决方案有两个层面。第一层如果客户允许在迁移窗口内临时关闭实例级别的强制加密迁移完成后再恢复但改了实例配置要重启服务业务影响比较大。第二层在 JDBC 连接串里设置信任证书参数让驱动跳过证书链校验。我们选了第二层但要在迁移方案里明确记录这个例外等割接完成后恢复标准连接方式。注意跨境网络环境下SSL 握手时的证书校验失败概率比本地网络高很多遇到类似问题先别怀疑账户密码直接检查 TLS 相关错误日志。6.2 导入导出向导的 OLEDB 驱动缺位问题这个项目里有个辅助环节——客户希望把某些参考数据表导出成 Excel 供业务人员核对。我们在客户的桌面环境上用了 SQL Server 导入导出向导结果报了一个很经典的问题未在本地计算机上注册 Microsoft.ACE.OLEDB.15.0 提供程序。这个问题的根源是 64 位环境下的导入导出向导缺少 Access 数据库引擎驱动。需要手动下载安装 Microsoft Access Database Engine 2016 Redistributable。安装时有个容易踩的坑如果电脑上同时装有 Office可能需要安装 32 位版本才能和 Office 共存而 SQL Server 导入导出向导默认是 64 位的两者会冲突。当时我的处理办法是不折腾桌面环境直接从 OceanBase 的运维工具里把参考数据导出为 CSV再通过 Excel 打开。省时省力还绕开了驱动问题。6.3 时区处理马来西亚没有夏令时的优势马来西亚使用固定时区UTC8没有夏令时切换这给时间字段的迁移省了不少麻烦。但项目里还是遇到了一个问题部分历史数据的时间戳是本地时间存储的而新写入的数据在应用层用了 UTC 时间两种时间混在同一张表里。迁移工具不会帮你识别这种业务层的语义混淆它只会按字段类型原样搬运。我们是在数据校验阶段发现部分行的时间排序异常排查后确认是应用层历史版本有问题。这种情况只能联合客户的开发团队修正数据规则迁移工具本身是解决不了的。顺带一提如果客户所在地区有夏令时迁移涉及DATETIMEOFFSET这类带时区偏移的类型时要格外注意偏移量是否正确转换。OceanBase 的时区处理机制和 SQL Server 的AT TIME ZONE行为并不完全一致。6.4 权限模型差异服务器级账号到租户级账号还有一个容易被忽略的坑是权限模型的差异。SQL Server 的登录名Login是实例级的用户名User是数据库级的而 OceanBase 的账号体系是租户级的。应用连接串里如果写了多个库共用同一个账号迁移后要重新设计账号模型。这个客户的系统里就有个老旧的共享账号从 2008 时代一直沿用到 2016跨了几个库读写数据。SQL Shift 并不会帮你把这种跨库账号关系自动映射我们是把账号拆分成两个一个只读账号用于报表查询一个读写账号用于业务系统各自最小权限。7. 割接执行细节与上线后的运行保障7.1 割接方案的步骤设计割接是整个迁移项目中最紧张的时刻我们设计的流程分五个阶段停止应用写入先通知业务方进入只读模式停止订单创建和修改操作追平增量让增量同步跑到延迟归零即源端和目标端数据完全一致校验最终一致性对核心交易表做最后一次行数和 checksum 校验切换连接串应用层配置从源库切换到 OceanBase并重启应用服务恢复业务写入观察应用启动日志和错误率确认无异常后开放全量业务马来西亚客户那边没有严格意义的业务低峰期概念因为他们的客户分布在东南亚多国办公时间跨度比较大。最后定在当地的周日凌晨 2 点执行到凌晨 5 点前完成只占用了约 3 小时的停机窗口。7.2 回滚方案到什么时候就不能回头了割接方案里最需要明确的不是怎么切而是什么时候不能退。我们的回滚规则是这样定的如果应用切换后 30 分钟内出现大面积连接错误或数据读写异常立刻切换回 SQL Server增量同步工具反向回灌从 OceanBase 到 SQL Server 的准实时数据。但如果应用稳定运行超过 2 小时就不再回滚走问题修复流程。这个时间窗口的设定是有依据的。30 分钟内的问题大概率是环境配置类、连接串类、权限类这类问题回滚成本低。超过 2 小时后的问题则很可能是业务逻辑兼容性类问题这类问题即使回滚到 SQL Server 也无法解决因为问题已经出在应用层对目标库SQL语法的适配上了。7.3 上线后的监控清单割接完成后不是万事大吉前两周的观察期非常关键。我们给客户配置了几个核心监控项慢查询日志每天拉取执行时间超过 300ms 的 SQL逐个 review活跃连接数关注连接池行为和 SQL Server 时代的差异OceanBase 的连接数上限和超时行为不同应用侧经常需要调参复制延迟如果还有数据同步任务确认增量链路健康告警阈值调整OceanBase 的告警项和 SQL Server 不一样比如事务日志空间、副本同步延迟都需要重新设置基准值经验之谈迁移后的第一周不要让客户立刻调整数据库参数。先让业务在默认参数下跑几天收集实际的负载特征再基于监控数据做针对性调优。一上来就猛调参数出了问题很难定位是新环境的问题还是参数的问题。7.4 遗留对象与清理计划最后还有一个收尾工作源库 SQL Server 实例要不要下线这个决定权在客户但我们给了明确的建议——保留至少一个月的并行运行期。期间 SQL Server 以只读方式运行不接收任何写入流量每天定时做一次备份确认 OceanBase 侧稳定后才彻底回收资源。同时要检查一个容易被忽略的点SQL Server 里的 SQL Agent 作业。客户之前有不少定时作业比如每天凌晨跑统计报表、数据归档这些作业在 SQL Server 里是独立于数据库的。SQLShift 不会迁移 Agent 作业需要在 OceanBase 侧用定时任务或者调度平台重新实现。我还特别提醒客户排查了应用层对 SQL Server 特性的隐性依赖比如用了NEWID()作为默认值的列、依赖SCOPE_IDENTITY()获取自增主键的逻辑、还有在WHERE条件里用ISNULL()的写法。这些在单库时代都没问题到了新环境就可能出现行为差异。整个项目从启动到最终割接完成前后花了大约五周。其中评估和测试环境准备工作两周全量迁移和校验一周增量同步和联调一周割接和观察一周。对 SQL Server 到 OceanBase 的这类迁移合理的时间分配应该是评估占四成、实施占四成、收尾占两成。很多项目翻车都是因为急着动手搬数据忽略了前期的兼容性分析和测试验证——磨刀不误砍柴工这句话在数据库迁移里是铁律。