☰
从SQL Server到OceanBase:游戏数据库迁移实战全记录
2026/10/22 23:42:34 网站建设 项目流程

前阵子刚把一家马来西亚游戏公司的核心交易库从 SQL Server 迁到了 OceanBase,整个项目做完之后,一直在陆陆续续收到同行问迁移细节。这个案例其实挺典型:游戏业务增长快、日志表和流水表膨胀迅速、许可证成本压顶、还要考虑东南亚多地区的容灾和扩展。如果你也正面临类似的“要不要换库”“从 SQL Server 怎么迁到 OceanBase”的纠结,这篇文章可以给你一条比较完整的参考路径。

文章不会只讲“我们用了什么工具”,而是把为什么要迁、迁之前怎么盘点存量对象、表结构怎么设计拆分键、全量增量同步怎么做、一致性校验怎么搞、上线切流怎么灰度、以及我们踩过的一些坑,全都展开讲清楚。对于数据库这块,同一个问题可能有一百种解法,我分享的是目前被验证过可行的方案,你拿去复现的时候,根据自己的业务体量做加减法就行。

1. 迁移前的项目背景:为什么要动这套跑了好几年的 SQL Server

1.1 这家马来西亚公司的真实痛点

先交代一下这家公司的情况。团队规模不大,核心是自研棋牌类社交游戏和几款休闲手游,业务覆盖马来西亚本地市场,也在往新加坡、印尼等东南亚国家扩展。原系统很纯粹:应用层以 Java/Go 微服务为主,数据层全部落在 SQL Server 上,高峰期大概有几十个实例,其中交易库和日志库占大头。

真正让他们下决心迁移的,是三个绕不开的问题。

第一个是许可证成本。SQL Server 按核心授权收费,随着业务增长,每次扩容都要额外购买许可,而且版本越高越贵。他们当时跑的是企业版,光许可证摊销下来的年成本已经顶得上一个小团队的口粮。对于游戏公司来说,这种固定成本是相当难受的,毕竟游戏业务有明显的波峰波谷,为了波峰时段去加买常驻许可,太不划算了。

第二个是扩展性瓶颈。SQL Server 的常用扩展路径无非是主从复制、读写分离、分库分表。前两者解决不了单库容量和写入吞吐的天花板,分库分表则要业务代码大量改造,而且分片规则一旦定死,后面再扩容又是噩梦。他们最大的几张玩家流水表,单表已经到了几亿行,日增千万行,索引重建和归档清理对线上影响越来越大。

第三个是跨机房容灾和弹性扩张的需求。东南亚不同地区的玩家延迟敏感,他们希望在多地域部署读写节点,同时要求故障时能快速切换。SQL Server 的 AlwaysOn 可用性组功能虽然强大,但配合多地域网络环境,运维成本和复杂度都不小,并且切换时的脑裂处理、见证服务器等一堆配置问题,小团队根本撑不住。

1.2 为什么选择 OceanBase 而不是继续升级 SQL Server

在选型阶段,他们其实也对比过 PostgreSQL、MySQL 分库分表方案、以及 OceanBase。最终选 OceanBase,有几个决定性因素。

最重要的一点,OceanBase 的分布式架构是原生内置的,不需要业务代码做分库分表。你仍然需要设计合理的拆分键,但对应用层来说,它看起来就是一个“超大号单库”,连接串、事务、JOIN 都还是常规用法。这一点对比 MySQL 分库分表中间件方案,能省掉大量业务改造。

其次是兼容性。OceanBase 同时兼容 MySQL 和 Oracle 两种模式。对于原来跑 SQL Server 的系统,绝大多数团队会选择切到 MySQL 兼容模式,因为游戏后端团队对 MySQL 的熟练度普遍更高,周边生态和运维工具也更熟悉。SQL Server 里的很多写法在 MySQL 模式下做一遍改造,成本是可控的。

再一个就是容灾能力。OceanBase 通过 Paxos 协议实现多副本强一致,自动选主和故障切换,配合全球部署能力,可以做到同城三副本或跨地域多活。对东南亚这种跨国家、跨地区部署场景,比手动维护 SQL Server 的可用性组轻松太多。

还有一个容易忽略的点:OceanBase 社区版对功能限制很少,这解决了他们成本上的最后一块心病。没有了按核心授权的许可成本,扩容改成加机器、加节点,弹性也好了很多。

1.3 迁移的核心认知:这事本质上是一次“方言改写 + 架构升级”

做完选型后,团队对迁移难度有过几个误判,这里先说清楚,免得你走弯路。

事故一,以为 SQL Server 的 T-SQL 和 MySQL 语法差不远,数据库迁移就是导数据。实际上,存储过程、函数、触发器、视图的改写工作量才是大头。如果源库有大量复杂的 T-SQL 逻辑,那迁移周期可能比预想的长得多。这家公司还好,核心业务逻辑大部分在 Java 服务里,数据库层的存储过程不算特别多,但即便如此,我们也花了大量时间做兼容性排查。

事故二,以为分布式数据库是把单机 Sql Server 表原样搬过去就行。实际上,拆分键的选择直接决定后续性能和运维体验。比如玩家流水表如果按流水号哈希,那按 player_id 查询就会变成跨节点扫描;正确做法是跟玩家维度相关的表统一按 player_id 做拆分键,并且放到同一个 TableGroup,让关联查询尽可能在一个节点内完成。

所以,整个迁移项目的定位应该是:先做一次彻底的存量 SQL 资产盘点,再做方言转换,然后才是数据搬迁和切流。数据搬运本身真的不难,难的是把“旧思维下的数据库对象”翻译成“分布式体系下的最优形态”。

2. 迁移前体检:SQL Server 存量对象兼容性盘点

动手迁之前,我强烈建议先花一到两周做一次完整的“数据库体检”。目的是把所有数据库对象、数据量、增长趋势、依赖关系摸清楚,然后生成一张兼容性改造清单。我们当时组织了一次类似“大规模代码审计”的动作,把线上几十个库的所有表、视图、存储过程、函数、触发器、序列、作业脚本全部捞出来,逐个比对。

这样做的原因很简单:分布式迁移最怕半路发现某个冷门存储过程里的语法无法转换,或者某个表的数据类型在目标端被悄悄截断。提前暴露这些问题,比迁到一半再返工要省太多时间。

2.1 数据类型映射:一张表搞定大部分工作量

SQL Server 和 MySQL 模式的数据类型大体对应,但细节差异不少,稍不小心就会埋雷。这里给出一份我们自己整理并最终落地的映射对照表。

SQL Server 类型OceanBase MySQL 模式类型注意事项
intint直接映射,注意自增列的种子值
bigintbigint直接映射,常用做主键
tinyinttinyintSQL Server 的 tinyint 是 0-255,MySQL 的一样,注意无符号设置
bittinyint(1)如果习惯布尔值,用 tinyint(1) 即可,查询层兼容
decimal/numericdecimal精度需保持一致,金额类字段建议统一 decimal(18,2) 以上
moneydecimal(19,4)避免直接用浮点类型存钱
smallmoneydecimal(10,4)同上,按实际金额范围调整
datetimedatetime精度上 SQL Server 约 3.33 毫秒,OceanBase 可到微秒级,无兼容问题
datetime2datetime映射后注意精度提升可能影响应用层比较逻辑
smalldatetimedatetime范围变大,但取值行为需要回归测试
datedate直接映射
timetime直接映射,注意精度和小数秒位数
char/ncharchar/varchar注意字符集和排序规则,尤其是定长字段的空格补齐行为
varcharvarchar关键是长度单位,SQL Server 按字符数,MySQL 模式默认也按字符,但要确认
nvarcharvarchar统一使用 utf8mb4 字符集,否则多语言字符可能乱码
text/ntextlongtextSQL Server 的 text/ntext 是历史遗留,迁移时顺便升级成 longtext
varbinaryvarbinary直接映射
binarybinary直接映射
uniqueidentifiervarchar(36)不建议用 char(36) 做主键,会产生随机写热点,后续详解
sql_variant不支持需要业务层枚举或 JSON 化改造
xml不支持建议序列化成 text/json,由应用层解析
geometry/geography不支持需要单独方案,比如改用文本坐标或地理空间中间件

这张表不能直接当成结论,每个字段的真实取值范围需要在迁移前抽样确认。比如 SQL Server 里 nvarchar 存的表情符号等四字节字符,到 OceanBase MySQL 模式如果字符集没设成 utf8mb4,就会变成乱码或报“Incorrect string value”。我们当时最大的坑之一就是字符集,后面单独讲。

2.2 T-SQL 方言改造的常见差异

数据类型映射是死板的,语法改写才是迁移中真正吃时间的地方。SQL Server 用的是 T-SQL,MySQL 模式是类 MySQL 方言,很多高频写法都要改。我把我们整理过的高频差异列一下,照着自己项目排查就可以了。

功能场景SQL Server 写法OceanBase MySQL 模式写法
获取当前时间GETDATE()、SYSDATETIME()NOW()、SYSDATE()
日期加减DATEADD(day, 1, col)DATE_ADD(col, INTERVAL 1 DAY)
日期差DATEDIFF(day, a, b)DATEDIFF(a, b) 注意参数顺序相反
字符串拼接a + bCONCAT(a, b) 或直接用
空值判断ISNULL(a, b)IFNULL(a, b) 或 COALESCE(a, b)
取前 N 条SELECT TOP 10LIMIT 10
行号/排名ROW_NUMBER() OVER(...)ROW_NUMBER() OVER(...) 写法一致
条件分支CASE WHEN ... THEN ... END一致
存储过程定义CREATE PROCEDURE ... AS BEGIN ... ENDCREATE PROCEDURE ... BEGIN ... END,注意 DELIMITER 处理
变量声明DECLARE @a INT; SELECT @a = 1DECLARE a INT; SET a = 1
临时表CREATE TABLE #temp建议用 CTE 或派生表,或使用真实临时表但注意连接池
分页OFFSET ... FETCHLIMIT ... OFFSET
自增列IDENTITY(1,1)AUTO_INCREMENT
全局唯一标识NEWID() / NEWSEQUENTIALID()UUID() 或应用层生成有序 UUID
字符串长度LEN(col)CHAR_LENGTH(col) 或 LENGTH(col),后者按字节
类型转换CAST(a AS INT)CAST(a AS SIGNED)

还有一个细节,SQL Server 的字符串拼接如果遇到 NULL,整个结果是 NULL,而 MySQL 模式下 CONCAT 函数会自动忽略 NULL。如果业务以前依赖了这个行为,改造之后就可能会出现数据不一致的小 bug。这种隐性差异只有靠充分回归测试才能发现。

2.3 字符集、排序规则与时区问题

字符集这块,海外游戏公司尤其要重视。SQL Server 里最常见的做法是用 nvarchar + Latin1_General_CI_AS 排序规则来同时存英文、中文、马来文等字符。迁到 OceanBase MySQL 模式后,统一方案是字符集 utf8mb4、排序规则 utf8mb4_general_ci 或 utf8mb4_0900_ai_ci。

这里要注意三个实操细节。

第一个是排序规则的大小写敏感问题。CI 是 case insensitive,如果你的表某些字段原来是 CS(case sensitive),迁移到 CI 排序规则后,查询条件里的字符串匹配行为会变化。比如原来区分“Abc”和“abc”的查询,迁移后可能合并到一起。我们的做法是,对每一个唯一索引、主键、以及频繁作为查询条件的字符串字段,逐条确认是否需要区分大小写,需要的话就在字段级指定 cs 排序规则。

第二个是长度计算。SQL Server 的 varchar(n) 的 n 按字符数计算,MySQL 模式下通过 CHARACTER SET 设置,也基本按字符数,但如果你在连接串里或者表定义里用了 BYTE 语义,就需要注意。字符集和数据实际字节数的差异,会影响索引前缀长度,可能导致“Specified key was too long”之类的报错。

第三个是时区。SQL Server 的 datetime 是不带时区信息的,OceanBase 的 datetime 同样不受会话时区影响,但 timestamp 类型会随会话时区变化。我们最终统一策略是:所有业务时间字段优先用 datetime 类型,应用层统一按 UTC+8(马来西亚时间)写入和读取;少数需要跨时区展示的字段用 timestamp 并在 JDBC 连接串里显式指定 serverTimezone。不要小看这个配置,如果两边各写各的,切流后玩家看到的活动开始时间可能差了好几个小时。

3. 迁移方案设计:全量 + 增量 + 灰度切换的三步走

体检做完,改造清单列出来之后,就开始设计具体的迁移路径了。我们最终采用的是标准的三阶段方案:全量数据迁移、增量日志同步、灰度切流。整个过程的目标是让业务中断时间控制在分钟级,甚至在流量低峰期做到几乎没有感知。

3.1 拆分键设计:分布式数据库最关键的一步

OceanBase 是分布式数据库,表默认需要选定拆分键。拆分键选的合不合理,直接决定了后续所有查询是“本地执行”还是“跨节点执行”。跨节点执行不是不行,但涉及分布式事务和网络传输,性能和稳定性都会打折。游戏场景里,最自然的拆分维度就是 player_id。

我们当时的表设计遵循了三条原则。

第一条:核心玩家维度表,统一按 player_id 哈希。玩家表、玩家背包表、玩家邮件表、玩家成就表、玩家交易流水表,全部以 player_id 为拆分键,并且放在同一个 TableGroup 里。这样玩家在一局游戏里的背包变更、邮件领取、成就更新,在同一个节点内部就能完成,避免了大量跨节点分布式事务。

第二条:配置类、枚举类、全局小表,设置为复制表。比如游戏版本配置、活动配置、服务器列表、道具字典这类“所有人都会读、几乎没人写”的表,复制表会在每个节点都保留一份完整副本。玩家登录时要读一堆配置,如果每次都是跨节点扫描,并发一大就会卡死,复制表完美化解这个问题。

第三条:日志类和流水类大表,用“拆分键 + 时间分区”的组合。比如玩家邮件日志表,拆分键仍然是 player_id,但同时按发送时间做 Range 分区。这样既能支撑玩家维度的查询,又能让归档和清理操作直接 drop 分区,不再需要逐行 delete。

这里还要特别提醒一下主键设计。如果原 SQL Server 表的主键是 uniqueidentifier(也就是日常说的 GUID),迁移的时候不要直接拿这个字段做拆分键。原因很简单:GUID 是随机生成的,哈希分布看起来均匀,但写入时会产生大量随机主键索引页分裂和缓冲区污染;而且游戏场景里很多查询是“查一个玩家的所有记录”,用 GUID 做主键根本无法把同一个玩家的行聚在同一个节点。我们的做法是保留 GUID 作为业务标识字段,但拆分键和主键统一用 player_id 或新增的自增 bigint 代理键。

3.2 全量迁移怎么做才快

全量迁移的目标是把 SQL Server 里已有的历史数据搬到 OceanBase。听起来简单,但一次性搬几十个库、上百亿行数据,一定要提前规划并行度和通道数。

我们用的路径是:结构迁移用官方工具做 DDL 转换和补充人工修正;数据迁移通过迁移服务和 DataX 同步通道混跑。核心做法如下:

首先按表的数据量分梯队。玩家流水、充值流水这类超大表走分片并行拉取:按照主键范围切成若干个段,每个段由独立的同步线程负责,并行度控制在 8 到 16。实测下来,单表 14 亿行左右的流水,在千兆内网环境下,全量拉取差不多一个晚上能跑完。这里有个细节,切分键一定要用主键,不能随便找一个存在大量空值的字段,否则容易出现某些分片跑很久、某些分片瞬间结束的严重倾斜。

其次是参数调优。全量导入时,批量写入的 batch size 很关键。我们试过从 100 到 1000 的若干档位,最终稳定在 500 行左右一提交,配合 16 并行,整体吞吐最高且对源库压力可控。batch size 太小会频繁提交,浪费网络 RTT;太大则可能导致事务版本过多,OceanBase 内存压力上升,甚至触发大事务限制。

再一个容易被忽略的点:索引要在数据导入之后再建。结构迁移阶段只建主键和唯一键,普通二级索引等数据导入完成后再通过并行 DDL 添加。否则一边写数据一边维护索引,导入速度会慢好几倍。这一点跟单机数据库的做法反直觉,但分布式数据库里,大批量导入时索引维护的开销更明显。

全量迁移过程中,源库的业务不能停。为了让源库数据发生变化时能够持续同步,我们同时开启了增量同步链路。这里就涉及到下一个阶段的细节。

3.3 增量同步与延迟控制

增量同步的原理,本质上是持续读取 SQL Server 的事务日志或 CDC(变更数据捕获)记录,然后把增删改操作实时回放到 OceanBase。我们从原来的 SQL Server 存量库开启 CDC,再通过同步服务把变更流转到 OceanBase。这个方案的好处是源库不需要介入业务代码,应用层完全不知道底层数据在复制。

增量链路运行中,最核心的监控指标就是同步延迟。正常运行时,延迟应该稳定在 1-5 秒内,如果出现分钟级延迟,大概率是这几类情况:某个超大事务一次性更新了上亿行,同步通道跟不上;源库日志文件暴涨,CDC 读取被拖慢;目标端 OceanBase 出现热点分片导致单节点写入压力过大。

我们遇到过最严重的一次延迟,是因为游戏运营团队在 SQL Server 上手动跑了一个大脚本,一次性给全服玩家发邮件,涉及几百万行插入,结果增量延迟直接飙到了 15 分钟。解决方式比较粗暴:给这种运维脚本设置分批提交,每批限制一万行,并且提前通知 DBA 关注同步延迟。如果你后续也会在源库跑批量脚本,强烈建议也做同样的限制。

另外,增量同步阶段需要时刻关注主键冲突。如果全量迁移结束后,源库还有少量“窗口期”数据未同步,而目标端已经能写入,就可能产生重复主键。我们在下文中会详细讲双写时的坑,这里简单提一句:进入切换阶段时,一定要先停掉目标端的非同步 DML 写入,或者应用层做开关控制。

3.4 数据一致性校验与灰度切换

数据搬过去和“数据真的对得上”是两回事。我们做了一整套校验流程,核心是分段聚合校验加抽样抽查。

分段聚合校验的思路特别简单,但非常有效:把一张大表按主键切成 N 个区间段,对每个段分别计算 count、sum(关键数值列)、min(主键)、max(主键),在 SQL Server 和 OceanBase 两边各跑一遍,然后比对结果。如果某个段对不上,再单独拉出这个段里的数据做差集分析。这个做法的好处是避免全表一行行比对,效率高,而且能快速定位差异范围。

抽样校验则侧重于字段内容本身。对每一张核心表,按 player_id 随机抽取一部分样本,把整行数据序列化成 JSON 后对比。重点考察字符串字段是否有乱码、金额字段精度是否丢失、时间字段时区是否正确。

切流顺序也是门学问。我们分了四步走:

第一步,切只读流量。先把内部报表平台、BI 数仓的数据读取切到 OceanBase 上,持续运行三到五天,验证读链路稳定性。

第二步,切部分边缘业务。例如游戏内公告查询、排行榜下载这类非核心读多写少的服务,切到 OceanBase 上,观察应用错误率和响应时间。

第三步,切核心写业务。选择凌晨低峰期,对源库开启只读,等待增量同步延迟追到 0,然后把应用连接串从 SQL Server 切到 OceanBase。此时源库仍然保留只读,方便回滚。

第四步,稳定观察 72 小时后,再彻底断开源库的复制链路。

这套流程虽然保守,但对游戏公司来说是最稳妥的。回滚手段时刻都在:只要源库只读,一旦 OceanBase 侧出现严重问题,把连接串切回去,业务就能恢复。实际上我们最终没有用到回滚,但预案完整带来的安全感,能让整个团队在切流时保持冷静。

4. 实战踩坑记录:那些文档里不会写的细节

这一章节是真正“用头发换来的经验”。很多问题如果不去实际迁移一遍,光看官方文档是根本发现不了的。

4.1 自增列和序列的迁移

SQL Server 里最常见的自增列写法是 IDENTITY(1,1),OceanBase MySQL 模式对应 AUTO_INCREMENT。DDL 翻译很简单,但有一个非常隐蔽的坑:自增列的当前种子值。

举个例子,原 SQL Server 的表里玩家 ID 已经自增到了 9527,但结构迁移只把表结构建出来了,没有同步“当前种子值”,目标端新写入的玩家 ID 可能从 1 开始。如果此时增量同步还在继续,新玩家插入时就直接主键冲突了。所以全量迁移完成后,必须立刻核对每一张自增表的最大 ID,并把 AUTO_INCREMENT 初始值设置到 max(id)+1。这一步我们在项目中写了自动化脚本来生成 ALTER TABLE 语句,手工做几百张表很容易漏。

如果你用的是序列对象而非自增列,OceanBase 里也有 CREATE SEQUENCE 的语法支持,但要注意下一值和缓存步长。SQL Server 老系统里手动管理序列的情况也很常见,这种建议直接用应用层改造替代。

4.2 触发器与存储过程的改造

SQL Server 里的触发器在游戏数据库里非常常见,比如玩家表插入后自动初始化背包、充值表插入后更新余额等。但分布式数据库架构下,触发器是尽量少用的,主要原因有两点:一是触发器在分布式环境中容易引发隐式的跨节点事务,出问题时定位困难;二是一旦批量导入数据,触发器会逐行执行,导致导入性能断崖式下跌。

我们的做法是逐条梳理触发器逻辑,把能在应用层做的事情全部挪到应用层。比如“玩家表插入后初始化背包”这个动作,改由微服务在业务代码里显式调用,失败还可以重试,日志也清晰。如果实在无法避免触发器,也建议把触发逻辑简化成消息事件或异步任务,千万不要把复杂的级联更新留到数据库里。

存储过程的改造更是工程量集中地。这家公司有大概 300 多个存储过程,其中一半以上涉及游标、动态 SQL、临时表。动态 SQL 在 MySQL 模式里用 PREPARE 语句可以实现,但调试麻烦;游标可以改写为 JOIN 或窗口函数;临时表尽量改成 CTE 或派生表。每一个存储过程都要做“逐行级语义对比”,压力测试后确认结果一致才能过。

4.3 死锁与热点问题排查

上线后遇到的第一个真实事故,是某个活动开启瞬间,大量玩家同时操作背包和货币资产,出现了一批死锁和超时。原因是虽然我们把表拆分到了不同节点,但同一个玩家的大并发更新仍然会在同一把行锁上竞争。而且这类活动通常有几个“全服唯一”的道具配置行,所有玩家来修改这个配置行状态,就形成了典型的热点行。

排查与解决的路径是这样的:通过 OceanBase 的日志和错误堆栈确认死锁涉及哪几张表、哪几个事务,然后用事务重放工具模拟,发现主要是事务更新顺序不一致导致的。比如事务 A 先更新货币表再更新背包表,事务 B 先更新背包表再更新货币表,两个事务在分布式锁下就容易互相等待。

解决办法有两个:一是调整应用代码,让同一个玩家维度内的多张表更新严格按照固定顺序执行(比如先背包后货币,全团队统一);二是把超高频更新的“库存扣减”改为乐观锁实现,即 UPDATE 时带 version 条件,返回 0 行则重试。实测下来,死锁基本消失,活动时段的错误率从千分之几降到了零。

热点行的问题,和表拆分键密切相关。如果一个“全服唯一”的配置项放在普通哈希表里,那所有写都打到这一个节点;要么用复制表让读取变快,要么拆分成多个子配置项分散写。游戏秒杀、领奖这类逻辑尤其要注意,建议压测阶段就要模拟全服并发的极限场景。

4.4 连接池、驱动、时区等细碎问题

这类问题单个看起来不大,但每一条都会在某个时刻咬你一口。

连接池:SQL Server 的 JDBC 驱动和 MySQL 驱动的 URL 参数体系完全不同。切到 OceanBase 后,用 HikariCP 或 Druid 的团队需要重写连接串,并且把 maxActive 和 initialSize 调小一些。SQL Server 连接池通常开得比较大,因为单机连接消耗可控;而 OceanBase 是分布式架构,连接实际分布在多台节点上,连接数开得过大反而导致节点间握手和内存消耗增加。我们在生产上把核心服务的最大连接数从 200 降到了 50,性能和稳定性反而更好。

驱动版本:一定要用与 OceanBase 版本配套的驱动包,不要拿通用 MySQL 驱动的旧版裸连。曾经有一个服务因为驱动版本过老,对 utf8mb4_0900_ai_ci 排序规则不识别,导致查询直接报错。

时区问题:连接串里显式加 serverTimezone=Asia/Kuala_Lumpur,不要依赖默认值。马来西亚是 UTC+8,如果服务器系统时间正好不是这个时区,又没有显式指定,时间字段就会偏差。

SQL 超时:SQL Server 默认允许长查询长时间运行,OceanBase 下的分布式查询如果设计不好,可能被超时机制打断。建议应用层超时阈值不要设置得太长,同时利用 OceanBase 的 SQL 诊断视图定期找那些执行计划走坏的查询。

5. 迁移完成后的稳定运行经验

切流完成只是新阶段的开始。上线后的一个月内,团队的主要工作变成了 SQL 性能治理和稳定性调优。如果你也准备迁移,下面这些经验可以直接复用。

5.1 上线初期的 SQL 性能治理

刚刚切到 OceanBase 时,由于执行计划管理和统计信息有一个收集和自适应过程,一部分在 SQL Server 上跑得飞快的查询,在 OceanBase 上的执行计划可能不走索引或产生不必要的分布式子计划。不要慌,这是迁移初期的常见现象。

我们的具体做法是:上线前三天,每天从 OceanBase 的 SQL 监控里拉取 Top N 慢 SQL,逐一分析执行计划,找出与 SQL Server 行为不一致的地方。常见的优化动作包括:

  • 补充统计信息或手动收集。
  • 对高频查询调整索引,利用 OceanBase 的局部索引和全局索引各自优势。玩家维度的查询尽量用局部索引,即索引与拆分键保持一致;如果不带拆分键的查询无法避免,就考虑全局索引,但要注意全局索引带来的写入放大。
  • 对应用层 SQL 做规范化,禁用 SELECT *,减少大字段返回。
  • 把原生 SQL 中针对 SQL Server 优化器写的 HINT 全部清理干净,那些 HINT 在 OceanBase 里不仅无效,还可能引发误解。

压测时就要把慢 SQL 治理跑起来,不要等上生产再打磨。

5.2 常见问题速查表

最后整理一份高频问题速查表,方便你后续排查。这些来自我们项目里真实遇到过的场景,每条都对应着一次真实的报警或人为排查。

现象可能原因解决动作
迁移后中文/表情符号乱码字符集不是 utf8mb4 或连接串字符集设置不对修改表字符集为 utf8mb4,连接串加 characterEncoding=utf8
插入报主键冲突自增种子值未调整到 max(id)+1逐表重置 AUTO_INCREMENT 初始值
增量同步延迟飙升源库批量大事务、CDC 拉取积压大脚本分批提交,同步通道扩容
查询结果和原来不一致大小写敏感排序规则变化、时区偏差逐字段确认 collation 和字段类型
分布式死锁多表更新顺序不一致统一应用层更新顺序或改乐观锁
活动热点行超时单一配置行高并发写复制表/配置项拆分/缓存前置
全量导入速度慢并行度低、batch size 不合理、索引过早创建分片并行、batch 500、后建二级索引
查询不走索引统计信息缺失、拆分键不匹配收集统计信息,检查拆分键与查询条件
连接数耗尽连接池配置过大或泄漏调低连接池上限,排查连接未释放
存储过程改造后行为不一致游标、临时表、动态SQL的语义差异逐行对比结果,尽量用 CTE 改写

5.3 迁移过程中的项目管理和人员组织

数据库迁移从来不只是 DBA 的事。我们的项目经理后来复盘时说,这个项目真正的成本曲线是“前期 SQL 改造占 40%,数据迁移占 20%,联调和切流占 40%”。所以,团队配置上至少要有三类角色:懂 SQL Server 业务逻辑的开发、熟悉 OceanBase 运维的 DBA、以及能做全链路压测的测试工程师。

每周的迁移例会只看三样东西:兼容性改造清单完成率、全量/增量同步的延迟趋势、目标端的慢 SQL 数量。这三样都稳定下来了,切流才有底气。不要用“感觉差不多了”来决定切换日,一定要用数据和指标说话。

有一点心得想特别分享:迁移窗口的核心不是“数据搬得快”,而是“尽可能缩短不可写的时间”。我们做到了分钟级,靠的是前面全量和增量两个阶段铺垫得足够扎实,切换时的动作其实就是把连接串改一下。

6. 迁移后的运维机制与效果回顾

项目上线至今运行了大半年,回看这次从 SQL Server 到 OceanBase 的迁移,最直观的收益是三个。

一是许可证成本归零。以前花钱买许可的预算转化为机器成本,而且机器是随着业务增长按需扩容的,月底结算起来账很清晰,不再有一次性的巨额支出。

二是扩容变得平滑。东南亚新开服时,直接在 OceanBase 集群里增加节点和租户资源,不再需要预先估算 SQL Server 许可和单机规格。游戏上线活动需要临时加资源时,几分钟就能完成操作,这在以前根本做不到。

三是容灾能力明显增强。多副本强一致和自动故障切换已经在一次机房网络抖动中被验证过,应用层无感知,玩家无感知。相比以前手动切换可用性组,运维同学终于可以睡个安稳觉。

如果现在让我重新做一次项目,我会在前期兼容性盘点阶段,就把“触发器改造”和“拆分键设计”这两件事提到最高优先级,它们比数据搬运本身更容易决定项目的成败。对于准备动手的团队,我的建议是:先找一张最核心的表做一次端到端试点迁移,从 DDL 转换跑通到增量同步、校验和切流模拟,这一套流程完整走一遍之后,再启动全量范围迁移,能把整体风险降低一大截。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询