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

📅 2026/7/21 1:49:12
【金仓数据库征文】不装中间件的 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 的 emoji4 字节字符整个字段 16 字节。你先记住它后面它是主角。三、mysql_fdw不搬数据先挂载迁移第一步不是搬数据是让金仓能看见 MySQL。三条 DDL 就够了。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。于是有了这一句。ALTER SERVER shop_mysql OPTIONS (ADD init_command SET NAMES utf8mb4);再查生日礼物回来了16 字节hex 结尾F09F8E81分毫不差。这个坑最可怕的地方是静默。不报错不中断行数一分不少只是每个 4 字节字符悄悄变成问号。如果迁移验证只对一下行数甚至只对一下金额总和它能一路蒙混过关直到某天用户投诉我备注里的表情怎么全变成问号了。迁移前先校准 fdw 字符集这是我用这次经历换来的第一条铁律。五、迁移一条 INSERT 搞定字符集校准好迁移就是水到渠成的一条语句。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.02MD5 指纹d4835ee4...完全相同。orders 也三项全等包括那行 emojiMD5572d7a44...相同。MD5 一致等于逐行逐列宣告无损。假如我没修字符集就迁移这里的 MD5 会立刻对不上。三级对账存在的意义就在这让静默损坏藏不住。但这里还有一个坑中坑。我的目标库建的是 MySQL 兼容模式而在 MySQL 兼容模式下||不是字符串拼接是逻辑或。我第一版对账脚本顺手用了id|||||name||...这种写法拼行算出来的 MD5 指纹其实是假的。我专门做过一个验证故意把目标表某一行的 email 改成错误值用||拼出来的指纹源和目标居然依旧相同逻辑或运算把真实的字段值吃掉了篡改完全漏检。换成concat(id,|,name,...)之后指纹立刻对不上篡改当场现形。一个会漏检数据损坏的对账脚本比不对账更危险它给你的是虚假的安全感。所以在金仓 MySQL 兼容库里做对账拼接一律用concat()别用||。七、双轨共存与增量追平真实迁移往往不能一刀切需要一段灰度窗口源库还在接单新库先并行验证。mysql_fdw 天然支持这种双轨因为那条通路一直在随时能对账。演示一下。我在 MySQL 侧新增了一单id99模拟灰度期还在进来的订单。对账立刻兜住了源库 11 行目标 10 行MD5 指纹对不上差异秒级暴露。接着增量追平INSERT INTO orders SELECT * FROM ft_orders WHERE id NOT IN (SELECT id FROM orders)只补目标缺的那一行。再对账11 对 11MD5 恢复一致。源库持续写入fdw 增量追平MD5 对账兜底这个循环就是平滑迁移窗口的核心机制。切换当天反复跑对账直到追平确认一致之后再把应用的连接串从 MySQL 换到金仓风险可控。八、结论我用 mysql_fdw 走通了一条不停机、不装中间件的 MySQL 到金仓的迁移路径。外部表建通路一条INSERT...SELECT迁数据三级对账验正确双轨增量做平滑源库只读可回退。最后再把那条教训放在这。行数一致不等于数据无损。fdw 的字符集要先校准对账拼接要用concat()这两个坑我都替你踩过了。