【金仓数据库征文】不装中间件的 MySQL→金仓在线迁移,mysql_fdw 全流程,和一个差点漏掉的 emoji

一、迁移的两种痛

把一个 MySQL 业务库迁到 KingbaseES,传统路径通常绕不开两样东西,一个导出导入的停机窗口,和一个额外部署的数据同步中间件。前者要业务方点头给时间,后者要多维护一个组件、多配一套规则。对中小库或者灰度试点来说,这两样都嫌重。

有没有更轻的办法?有,金仓自带的 mysql_fdw,全称 MySQL Foreign Data Wrapper,外部数据包装器。它让金仓能像访问本地表一样直接读远端 MySQL,于是迁移可以简化成一条 INSERT ... SELECT,不导文件,不装中间件,源库不停机。

这篇我在鲲鹏服务器上把这条路走通了。而且在最后的对账环节,还抓到了一个只数行数绝对发现不了的静默数据损坏。这个坑,才是整篇文章我最想分享的东西。

二、造一个「有刺」的迁移源

为了让测试有意义,我在 MySQL 侧造了一个电商库 shop,特意埋了几类迁移里最容易翻车的数据。

两张表,customers 8 行,orders 10 行。刺埋在这些地方,中文的名字和城市,NULL 的邮箱,decimal(12,2) 的精确金额,datetime 时间戳。最关键的一根刺在 orders 里,有一行 note 写的是 生日礼物🎁,带一个 utf8mb4 的 emoji,4 字节字符,整个字段 16 字节。你先记住它,后面它是主角。

三、mysql_fdw,不搬数据先挂载

迁移第一步不是搬数据,是让金仓能看见 MySQL。三条 DDL 就够了。

SQL 复制代码
CREATE EXTENSION mysql_fdw;
CREATE SERVER shop_mysql FOREIGN DATA WRAPPER mysql_fdw
  OPTIONS (host '127.0.0.1', port '3306');
CREATE USER MAPPING FOR system SERVER shop_mysql
  OPTIONS (username 'fdw', password 'Fdw#2026');

然后为 MySQL 表建外部表,把 MySQL 类型映射成 KES 类型。挂载完成,直接在金仓里 SELECT。

\d ft_customers 能看到外部表的完整定义,列类型、所属 Server,还有 OPTIONS (dbname 'shop', table_name 'customers')。执行 SELECT 的时候,数据物理上还在 MySQL 里,SQL 由金仓执行、实时拉取。类型映射很自然,MySQL 的 int、decimal(12,2)、datetime、tinyint,分别落到 KES 的 int、numeric(12,2)、timestamp、smallint。

这一步真正的价值,是先建立了通路。有了通路,迁移、对账、增量追平全在同一条通路上完成,不需要第二个工具。

四、差点漏掉的 emoji

正当我准备一把梭迁移的时候,随手验了一下那行 emoji。

出事了。

金仓通过 fdw 读到的 note 是 生日礼物?,emoji 变成了问号。用字节数一验,事情就清楚了。MySQL 源里是 16 字节,hex 结尾 F09F8E81,正是 🎁 的 utf8mb4 四字节编码。fdw 读到的只有 13 字节,hex 结尾 3F,就是一个问号。

那 4 个字节,在传输途中被悄悄吃掉了。根因是 mysql_fdw 默认用 3 字节的 utf8 字符集连接 MySQL,遇到 4 字节的 utf8mb4 字符,emoji、部分生僻字、CJK 扩展区汉字,就地转成问号。

我先试着给 server 加一个 character_set 选项,被拒了,它不在合法选项列表里。但报错的 HINT 里列出的合法选项中,有一个 init_command,它能在每次建立连接时执行一条 SQL。于是有了这一句。

SQL 复制代码
ALTER SERVER shop_mysql OPTIONS (ADD init_command 'SET NAMES utf8mb4');

再查,生日礼物🎁 回来了,16 字节,hex 结尾 F09F8E81,分毫不差。

这个坑最可怕的地方是静默。不报错,不中断,行数一分不少,只是每个 4 字节字符悄悄变成问号。如果迁移验证只对一下行数,甚至只对一下金额总和,它能一路蒙混过关,直到某天用户投诉,我备注里的表情怎么全变成问号了。迁移前先校准 fdw 字符集,这是我用这次经历换来的第一条铁律。

五、迁移,一条 INSERT 搞定

字符集校准好,迁移就是水到渠成的一条语句。

SQL 复制代码
CREATE TABLE customers (...);      -- KES 本地表
INSERT INTO customers SELECT * FROM ft_customers;   -- 8 行
CREATE TABLE orders (...);
INSERT INTO orders SELECT * FROM ft_orders;         -- 10 行

INSERT 0 8INSERT 0 10,数据从 MySQL 直接流进金仓本地表。没有中间的 CSV 文件,没有 mysqldump,没有第三方同步工具,外部表本身就是管道。迁移完成后,本地表是纯粹的 KES 原生表,不再依赖 MySQL。

六、对账,为什么只数行数会出事

迁移完成不等于迁移正确。前面的 emoji 事件已经证明,数据可以在行数完全一致的情况下悄悄损坏。所以我做了三级对账,一级比一级严。

对账级别 能抓住什么 会漏掉什么
① 行数 count 整行丢失、重复 行在但内容错,比如 emoji 变问号
② 数值列 SUM 金额、数量类错误 字符串损坏、时间偏移
③ MD5 全表指纹 逐行逐列的任何差异

第三级是关键。我在金仓里对外部表(源)和本地表(目标)执行完全相同的 SQL,把每行拼成字符串、排序后求 MD5。因为两边都由金仓引擎执行,口径完全一致,只要有任何一个字节不同,指纹就会不同。

结果,customers 三项全等,8 行对 8 行,金额总和 374550.02 对 374550.02,MD5 指纹 d4835ee4... 完全相同。orders 也三项全等,包括那行 emoji,MD5 572d7a44... 相同。MD5 一致,等于逐行逐列宣告无损。假如我没修字符集就迁移,这里的 MD5 会立刻对不上。三级对账存在的意义就在这,让静默损坏藏不住。

但这里还有一个坑中坑。我的目标库建的是 MySQL 兼容模式,而在 MySQL 兼容模式下,|| 不是字符串拼接,是逻辑或。我第一版对账脚本顺手用了 id||'|'||name||... 这种写法拼行,算出来的 MD5 指纹其实是假的。我专门做过一个验证,故意把目标表某一行的 email 改成错误值,用 || 拼出来的指纹,源和目标居然依旧相同,逻辑或运算把真实的字段值吃掉了,篡改完全漏检。换成 concat(id,'|',name,...) 之后,指纹立刻对不上,篡改当场现形。

一个会漏检数据损坏的对账脚本,比不对账更危险 ,它给你的是虚假的安全感。所以在金仓 MySQL 兼容库里做对账,拼接一律用 concat(),别用 ||

七、双轨共存与增量追平

真实迁移往往不能一刀切,需要一段灰度窗口,源库还在接单,新库先并行验证。mysql_fdw 天然支持这种双轨,因为那条通路一直在,随时能对账。演示一下。

我在 MySQL 侧新增了一单,id=99,模拟灰度期还在进来的订单。对账立刻兜住了,源库 11 行,目标 10 行,MD5 指纹对不上,差异秒级暴露。接着增量追平,INSERT INTO orders SELECT * FROM ft_orders WHERE id NOT IN (SELECT id FROM orders),只补目标缺的那一行。再对账,11 对 11,MD5 恢复一致。

源库持续写入,fdw 增量追平,MD5 对账兜底,这个循环就是平滑迁移窗口的核心机制。切换当天反复跑对账直到追平,确认一致之后,再把应用的连接串从 MySQL 换到金仓,风险可控。

八、结论

我用 mysql_fdw 走通了一条不停机、不装中间件的 MySQL 到金仓的迁移路径。外部表建通路,一条 INSERT...SELECT 迁数据,三级对账验正确,双轨增量做平滑,源库只读可回退。

最后再把那条教训放在这。行数一致,不等于数据无损。fdw 的字符集要先校准,对账拼接要用 concat(),这两个坑,我都替你踩过了。

相关推荐
名字还没想好☜3 小时前
Python f-string 进阶:数字格式化、对齐填充、调试 = 号与嵌套表达式
开发语言·数据库·python·字符串格式化·f-string
ltl3 小时前
Serverless 数据库弹性理论:Neon 与 Aurora Serverless v2
数据库
Dxy12393102164 小时前
Python 如何使用 MySQL 的事务
python·mysql
marvelyu8 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
ZCBUS实时计算8 小时前
金融证券实时数仓建设实践:轻量化实时计算平台落地,实现交易数据端到端秒级处理
大数据·数据库·数据仓库·金融·flink·dba·etl
zd2005728 小时前
海洋微生物数据库
数据库·宏基因组
海上小飞龙9 小时前
Redis 分布式锁原理:从 SET NX EX 到 Redisson 看门狗
数据库·redis·分布式
xiaoye-duck9 小时前
MySQL 表约束全解:从基础约束到外键关联规则
数据库·mysql
Hammer_Hans9 小时前
DFT笔记98
java·开发语言·数据库
迪康Defender10 小时前
从静态存储到动态流转:终端透明加密两种模式实战解析
运维·服务器·网络·数据库·其他