首页 / 资讯中心 / 文章详情

MySQL到PostgreSQL迁移实战:Docker+pgloader全流程指南

MySQL到PostgreSQL迁移实战:Docker+pgloader全流程指南 ★ FEATURED ARTICLE
上个月我刚用 Docker pgloader 的组合完成了一次 MySQL 5.7 到 PostgreSQL 15 的整库迁移。从容器编排到数据核对大概花了一天真正跑数据迁移的时间不到半个小时整个过程比预想顺畅很多。不过顺畅是有条件的——如果你直接拿默认配置一把梭后面大概率会在时区、序列、外键这几个地方反复返工。这篇文章把我这次迁移的完整过程、配置文件和踩过的坑都整理出来给准备做 MySQL 到 PostgreSQL 迁移的同学一个能直接照搬的参考也聊聊 pgloader 在真实项目里的优势和边界。适合看这篇的人有三类已经在做迁移方案选型、正在评估 pgloader、或者手里正压着一个 MySQL 老库想换到 PostgreSQL 但不知道从哪下手的。文章不绕弯子直接讲环境怎么编排、配置文件怎么写、日志怎么读、迁移完要补哪些动作以及我实际遇到过的几个典型坑。1. 迁移背景与选型为什么是 Docker pgloader 这套组合1.1 一次典型的 MySQL 迁 PG 需求长什么样这次的业务背景是一个老项目库在 MySQL 5.7 上跑了四五年表有 60 多张数据量不算大总量大概 20GB 出头。业务方提迁移需求的原因很实在随着查询复杂度上来MySQL 在窗口函数、CTE 递归、JSON 检索这些场景写得非常别扭项目组用的新框架对 PostgreSQL 的支持也明显更成熟。迁移窗口给了三个小时要求是表结构、数据、索引、自增主键行为都要完整保留。说白了就是换个库业务逻辑和应用代码尽量少改。目标版本定在 PostgreSQL 15不需要跨版本纠结。这种需求核心就三点一是把 MySQL 整套 schema 转换成 PostgreSQL schema二是把存量数据搬过去三是让应用能无感切过去。第三个其实是最难的因为序列值、外键约束、时间类型这些细节会直接决定应用切过去之后有没有坑。1.2 为什么不用 mysqldump、Navicat、手工建表很多人的第一反应是 mysqldump 导 SQL 再在 PostgreSQL 里执行。这个方案在试验环境玩一下没问题真上了带业务数据的库基本走不通。MySQL 的 SQL 方言和 PostgreSQL 差异很大引擎注释、反引号、自增列定义、字符集设置导出来直接跑全是报错。就算把 CREATE TABLE 手工改成 PG 语法数据类型映射也是一大堆活TINYINT、UNSIGNED INT、DATETIME、ENUM 这些和 PG 类型压根不是一一对应。Gui 工具和在线迁移服务我也试过几千行的表没问题到了几十万上百万行的业务表就开始吃力长连接容易断特殊类型支持也弱。手工建表更不现实60 张表、几百个字段、几十个索引一个个手敲光是对照元数据就是巨大工作量还容易漏外键。pgloader 的优势是直接读源库元数据内部有一套成体系的 MySQL 到 PostgreSQL 类型映射规则自动生成建表语句、索引和外键然后数据流式批量写入。一条命令完成 schema 转换和数据迁移两个环节中间步骤全部自动化。对于中小型库的一次性全量迁移它是最省事的方案。1.3 pgloader 到底做了什么说直接点pgloader 不是把 MySQL 的 SQL 翻译一遍再执行它分两条线工作元数据线连接 MySQL 后读取 information_schema拿到每张表的字段、类型、默认值、索引、外键信息然后按内置映射规则生成 PostgreSQL 的 CREATE TABLE、CREATE INDEX、ALTER TABLE ADD CONSTRAINT 语句。数据线对每张表做流式读取按主键或者行号分段拉取数据然后在内存里完成类型转换再用 PostgreSQL 的 COPY 协议批量写入。这里用的是 COPY不是逐行 INSERT所以大表迁移速度可以做到很快。它还有一个优点迁移过程会实时输出每张表的进度、行数、错误数和耗时。哪个表有问题一眼就能看到不用迁移完再去查日志。边界也要说清楚。pgloader 是批量全量迁移工具不是增量同步工具。它不支持追 binlog不能持续复制 MySQL 的增量变更。如果业务要求不停机无缝迁移单靠 pgloader 做不到后面可能要接一套 CDC 方案。但如果是可以接受短暂写停的停机迁移pgloader 完全够用。2. 环境编排把 MySQL、PostgreSQL、pgloader 三个容器转起来2.1 网络方式怎么选用 Docker 编排的一个核心问题三个容器要能互相访问。这里有两个主流做法。Linux 环境下直接--network host最省事pgloader 容器里访问localhost:3306和localhost:5432就行。但 Docker DesktopmacOS 和 Windows对 host 网络支持有限更稳妥的是用 docker compose 建立一个自定义网络让容器之间通过服务名互相访问。我这次是在一台 Linux 服务器上跑的为了以后其他人能直接复用最后还是写了 compose 文件用自定义网络。源 MySQL、目标 PostgreSQL、临时 pgloader 三个服务放在同一个网络里服务名就是访问地址不依赖外部端口暴露干净很多。services: mysql-src: image: mysql:5.7 container_name: mysql-src environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: appdb MYSQL_USER: migrator MYSQL_PASSWORD: migpass command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_bin ports: - 3306:3306 pg-dst: image: postgres:15 container_name: pg-dst environment: POSTGRES_PASSWORD: pgpass POSTGRES_DB: appdb ports: - 5432:54322.2 源库账号权限与目标库准备迁移账号不要直接用 root专门建一个最小权限账号比较稳妥。pgloader 只需要读元数据和读表数据SELECT 和 SHOW VIEW 就够了CREATE USER migrator% IDENTIFIED BY migpass; GRANT SELECT, SHOW VIEW ON appdb.* TO migrator%;如果源库是 MySQL 8默认认证插件是 caching_sha2_password老版本的 pgloader 会连不上。创建账号时显式指定 mysql_native_passwordCREATE USER migrator% IDENTIFIED WITH mysql_native_password BY migpass; GRANT SELECT, SHOW VIEW ON appdb.* TO migrator%;目标库这边直接用 postgres 超级用户运行 pgloader。因为 pgloader 要在目标库建表、建索引、加约束、重置序列普通用户很容易因为权限不够中断迁移不值得在这个环节卡住。还有一个容易忽略的点MySQL 容器启动时要把字符集指定成 utf8mb4。源库如果是 5.7 且历史建库时用了默认的 latin1数据读出来转码会出问题。即便库本身是 utf8mb4连接层没有显式指定也可能在遇到 emoji 这类四字节字符时转换失败。2.3 拉镜像、挂配置文件、执行pgloader 官方镜像直接拉最新的docker pull dimitri/pgloader:latest把配置文件放在当前目录下例如 pgloader.load然后一次性运行docker run --rm \ --network app-migration_default \ -v $PWD/pgloader.load:/pgloader.load:ro \ dimitri/pgloader:latest \ pgloader /pgloader.load加--rm是因为迁移容器是一次性任务跑完就销毁没必要保留容器状态。配置文件以只读方式挂进去也是防止误改配置。用 compose 的话也可以加一个 pgloader-runner 服务depends_on等 MySQL 和 PG 都起来之后再执行但一次性 run 更符合它的临时任务定位。3. pgloader 配置文件逐行拆解从 LOAD DATABASE 到 CAST 规则3.1 一个可以拿来就用的完整配置这次迁移用的配置如下我一行行拆开讲LOAD DATABASE FROM mysql://migrator:migpassmysql-src:3306/appdb INTO pgsql://postgres:pgpasspg-dst:5432/appdb WITH include drop, create tables, create indexes, reset sequences, workers 8, concurrency 1, multiple readers per thread, rows per range 50000, batch rows 10000, batch size 100MB, prefetch rows 1000 SET PostgreSQL PARAMETERS maintenance_work_mem 128MB, work_mem 12MB CAST type datetime to timestamptz keep default, type tinyint to smallint using tinyint-to-smallint, type bigint when unsigned to bigint, type varchar when precision 255 to text, type json to jsonb;连接串的格式是标准的 URL 风格。mysql://用户:密码主机:端口/库名目标库同理。注意这里的主机名要写 compose 里的服务名 mysql-src、pg-dst不是 localhost因为容器之间靠服务名解析。3.2 WITH 子句里的动作清单WITH 子句是整个迁移行为的控制面板几个关键项一定要明白含义include drop目标库中已存在的同名表先 DROP 掉再重建。这让迁移具备可重跑性不用每次手动清库。缺点是如果目标库有别的业务表一定要确认不会误删。create tables按 MySQL 元数据自动生成建表语句。create indexes自动迁移索引包含主键、唯一索引、普通索引。reset sequences把 PG 里自增列对应的 sequence 设置到当前表最大值这是模拟 MySQL AUTO_INCREMENT 行为的关键动作。workers 8并行处理表的 worker 数量相当于最多同时迁移 8 张表。concurrency 1磁盘并发级别影响数据落盘的并发度。multiple readers per thread每个线程内部用多路读取数据源对拉取大表有帮助。rows per range 50000每 50000 行作为一个数据分块。分块太小会导致通信开销大太大则单块处理时间过长。batch rows 10000、batch size 100MB、prefetch rows 1000这三个控制 COPY 批量写入的阈值任一条件满足就会执行一次批量提交。这些参数直接影响迁移速度。默认配置不是不能跑但小库和大库的合理取值差别很大后面性能调优章节我会给推荐范围。3.3 为什么默认类型映射不够要写 CASTpgloader 自带一套默认映射规则常见转换如下MySQL 类型PostgreSQL 默认映射说明TINYINTSMALLINT如果用 TINYINT(1) 表示布尔需要显式处理INT / INTEGERINTEGER范围对应BIGINT UNSIGNEDBIGINT无符号到有符号值超 21 亿会溢出VARCHAR(255)VARCHAR(长度)同长度迁移VARCHAR(255)TEXT避免超出 PG 行内大字段限制DATETIMETIMESTAMPTZ需注意时区语义TIMESTAMPTIMESTAMPTZ同上JSONJSONB行为上有差异通常可接受ENUMTEXT不加 CHECK 的话约束会丢失默认规则覆盖了大部分场景但实际业务总有特殊情况。举个例子有的表设计时用 TINYINT(1) 存布尔应用代码拿到的却是 0/1。PG 里最接近的类型是 boolean默认映射会变成 smallint应用切过去之后如果期望的返回类型是布尔就会出问题。再比如 DATETIMEMySQL 的 DATETIME 不带时区纯存一个本地时间字符串。PG 的 timestamptz 是带时区语义的。直接映射后如果连接时区和写入时数据本意不一致时间值可能整体偏移。这类问题不是 pgloader 能自动判断的需要业务方确认数据的时区语义然后手动指定 CAST。3.4 迁移前要做的表结构规整这是很多教程不会提、但实战中很重要的环节。MySQL 在 Linux 上默认lower_case_table_names0表名大小写敏感有些历史表名带大写字母。PostgreSQL 对未加引号的标识符会折叠成小写迁移后如果应用代码用带引号的大写表名访问就会找不到表。我这次的源库比较干净都是小写表名所以没有处理。如果你的源库有大写表名迁移前最好先统一成小写否则后面应用层到处改 SQL 会非常痛苦。另外迁移前顺手查一下有没有无主键的表。pgloader 对无主键表的读取方式会退化成全表扫描式读取效率明显下降而且切分数据块时没有主键边界分块逻辑更笨重。有主键的表迁移速度和稳定性都高一个档次。4. 实际操作启动迁移、读 pgloader 日志、盯输出报表4.1 执行迁移时的终端输出长什么样运行 pgloader 命令后终端不会安安静静的。它会先打一段获取元数据的信息然后进入数据迁移阶段输出一张实时刷新的进度表。大概长这样table name errors rows bytes total time ------------------------------- --------- ------- --------- -------------- fetch meta 0 60 2.1kB 0.080s create tables 0 60 6.8kB 0.102s create indexes 0 0 0.0kB 0.000s reset sequences 0 0 0.0kB 0.001s ------------------------------- --------- ------- --------- -------------- users 0 18432 4.6MB 2.852s orders 0 120000 18.3MB 11.203s products 0 3050 1.2MB 0.853s这张表信息量很大。errors列是最关键的指标——只要不为 0就说明有数据行没有成功写入。total time能看每张表耗时如果某张表行数不多但跑了几分钟大概率有问题。4.2 如何判断迁移是否真的成功一个常见的误解是命令执行完没有报错就认为迁移成功了。实际上 pgloader 的退出码和日志都要看。正确做法是三步第一步看报表里所有表的 errors 列是否全为 0。第二步到目标库跑一个全库对象对比看表数量、索引数量、外键数量是否和源库一致。第三步才轮到数据层面的核对这个下一章专门讲。如果 errors 不为 0pgloader 会把失败的具体行信息打到日志里。常见原因有几种目标表约束冲突、源库存在非法字符编码、类型转换溢出。重点说一下类型溢出——MySQL 的 BIGINT UNSIGNED 最大值是 18446744073709551615而 PG 的 BIGINT 有符号最大值 9223372036854775807。如果源库真有这么大的值直接转 BIGINT 会溢出pgloader 会报错并把该行标记为失败。虽然实际业务里这种极端值很少见但审计表、统计表偶尔会有。4.3 迁移中途失败怎么办pgloader 没有严格意义的断点续传功能但它失败时已经提交的数据不会回滚。重新执行整库迁移时include drop会先把目标表清掉再重建所以重跑是安全的。我的经验是全库正式迁移前先单独试跑两张有代表性的表。一张是最大的业务表验证数据量和耗时一张是字段类型最杂的表验证 CAST 规则是否覆盖了所有特殊类型。这样可以尽早暴露问题避免全库跑到一半才发现映射错误。pgloader 支持在配置里过滤表用INCLUDING ONLY TABLE NAMES MATCHING或者EXCLUDING TABLE NAMES MATCHING控制迁移范围。试跑时我会只迁一张表LOAD DATABASE FROM mysql://migrator:migpassmysql-src:3306/appdb INTO pgsql://postgres:pgpasspg-dst:5432/appdb WITH include drop, create tables, create indexes, reset sequences INCLUDING ONLY TABLE NAMES MATCHING users确认这张表没问题再把过滤条件去掉跑全量。这个习惯帮我省了不少时间。4.4 迁移期间源库侧的影响控制迁移不是零成本的源库在高并发读取数据时会有明显的磁盘 IO 和网络流量。如果源库是业务正在用的在线库最好选在低峰期执行。更关键的一点全量迁移期间如果源库还在持续写入迁移结果和业务真实状态之间必然有差距。所以正式切换前通常需要让应用进入只读维护模式停止写入再跑一次最终迁移或者至少在停止写入后对关键表做一轮增量补偿。5. 迁移完成不等于结束序列、约束、索引、行数一个都不能少5.1 序列复位是自增行为的核心MySQL 的 AUTO_INCREMENT 在 PG 里对应 sequence。pgloader 的reset sequences会自动处理但为了保险我每次迁移完都手动确认一遍。原理很简单sequence 的当前值如果小于表里已有的最大 id下一次插入就会触发主键冲突。正确状态是 sequence 当前值大于等于 max(id) 1。手动复位语句SELECT setval(users_id_seq, (SELECT COALESCE(MAX(id), 1) FROM users));如果表很多可以用一个脚本批量生成并执行这些语句。注意一个细节如果迁移过程中某张表因为 error 跳过了一部分行之后手动补齐时 max(id) 可能还没达到源库最大值那 sequence 还是要以源库最大值来设置而不是以目标表当前值。5.2 外键和索引验证pgloader 会建外键但建的过程可能因为表间依赖顺序问题出现意外。建议跑完迁移后执行这条查询检查是否有外键处于未验证状态SELECT conname, convalidated FROM pg_constraint WHERE contype f;如果结果里有convalidated f的外键需要显式执行验证ALTER TABLE 表名 VALIDATE CONSTRAINT 约束名;索引侧的核对更直接在 MySQL 里SHOW INDEX FROM 表名在 PG 里查pg_indexes两边逐表对比索引名称和字段组合。pgloader 偶尔会跳过一些特殊索引例如 MySQL 里的函数索引、前缀索引这些在 PG 里对应关系特殊依赖默认迁移很容易遗漏。5.3 行数对比与数据抽查行数对比看起来简单实际操作时大表 count 会很慢。我一般的做法是分两步。第一步先对比每张表的 min(id)、max(id) 和 count可以在两边并行执行快速找出明显不一致的表。MYSQL_CMDdocker exec mysql-src mysql -umigrator -pmigpass -N -e PG_CMDdocker exec pg-dst psql -U postgres -d appdb -tA for tbl in users orders products; do m_count$($MYSQL_CMD SELECT COUNT(*) FROM appdb.$tbl) p_count$($PG_CMD -c SELECT COUNT(*) FROM $tbl) echo $tbl mysql$m_count pg$p_count done第二步是抽样抽查字段值。我习惯对每张表取最小 id、最大 id、中间 id 的三行逐字段对比。如果表有 update_time 这类字段也可以按时间范围抽几段。这一步不能省因为行数一致不代表每行内容都一致尤其要注意字符串尾部空格、浮点精度这类隐藏差异。5.4 应用连接切换最后做数据层面全部核对通过后才建议动应用。切换连接串时注意修改的不只是数据库名称还有端口和用户名。MySQL 默认 3306PG 默认 5432端口写错是切换后最常见的低级错误。我的习惯是先让测试环境完全切到 PG 跑一天重点看业务日志里有没有类型转换、函数不存在、SQL 语法不兼容的报错。确认没问题后再切生产并且保留两套连接配置随时可以回切直到新库稳定运行一周以上。6. 踩坑实录我在这类迁移里遇到过的 6 个典型问题6.1 时区偏移DATETIME 到底该对应 timestamp 还是 timestamptz这是迁移后最隐蔽的问题因为不是所有行都会错而是只有跨越夏令时或者应用按 UTC 写入的数据会错。MySQL 的 DATETIME 本质就是存一个墙上时间不带任何时区语义比如存2024-06-01 12:00:00。PG 的 timestamptz 存储的是 UTC 时刻显示时按会话时区转换。如果应用写入时把业务时间当 UTC 存那读出来单独看也许是对的如果应用写入的是北京时间但 PG 会话时区是 UTC查询结果就会偏离 8 小时。pgloader 默认把 DATETIME 映射成 timestamptz这在语义上其实并不严谨。我的做法是迁移前先确认业务的时区规范如果业务所有时间都以某个固定时区的本地时间存储直接 CAST 成 timestamp 不带时区完全保留原值CAST type datetime to timestamp keep default如果业务需要跨时区才考虑 timestamptz但前提是源库的 DATETIME 语义与目标时区定义一致。这个决策必须由业务方拍板DBA 和技术负责人要在迁移前明确对齐否则切换后排查数据漂移会非常痛苦。6.2 TINYINT(1) 被当成布尔但实际存的是数值不少历史项目用 TINYINT(1) 表达布尔字段0 表示 false1 表示 true。但后来业务扩展有人往里写了 2、3 这种状态值。这种情况非常常见。pgloader 默认把 TINYINT 映射成 SMALLINT数据层面是安全的。但如果应用层在 PG 端期望 boolean 类型就要用转换函数CAST type tinyint when precision 1 to boolean using tinyint-to-boolean问题在于tinyint-to-boolean这个函数会在值不是 0/1 时处理出意外结果。所以我强烈建议迁移前先用 SQL 检查源库所有 TINYINT(1) 字段的实际取值分布确认只有 0 和 1再考虑转 boolean。只要发现任何一条非 0/1 记录就保留 smallint让应用层自行处理。6.3 varchar(300) 建索引超过 PG 限制源库有一张用户表code 字段是 varchar(300)MySQL 侧建了普通索引没问题。迁移到 PG 后pgloader 建索引直接报错提示 index row size 超限。原因在于 PG 的 btree 索引默认对单列长度有限制一个索引条目不能超过约四分之一页换算下来 text/varchar 超过一定字节就建不了普通 btree 索引。处理方案有三种。如果字段实际长度有上界就收缩字段长度到合理范围再建索引。如果字段语义上适合等值查询改用 hash 索引。如果必须支持前缀匹配建 text_pattern_ops 或表达式索引CREATE INDEX ON users USING btree (code text_pattern_ops);这类问题没有统一解要回到业务查询模式来定。迁移验证阶段如果发现索引报错先不要急着改 pgloader 配置应该把表结构和相关查询一起拉出来分析。6.4 双向外键导致约束创建失败有一张关系表A 表的外键指向 B 表B 表的外键反过来指向 A 表经典的一对一关联设计。pgloader 在创建外键时按依赖顺序处理遇到这种循环依赖就会出现约束无法建立的情况。pgloader 的日志这时会把完整的 CREATE TABLE 和 ALTER TABLE 语句打出来能看到是哪条外键失败的。解决方案是绕过自动建外键迁移完手工补约束。具体操作在 pgloader 配置里排除掉这两个表的约束创建或者直接对这两张表先不建外键数据迁移完成后用事务包裹手动执行 ADD CONSTRAINT。注意 PostgreSQL 的外键是支持同一个事务里多次 ALTER 的循环引用也可以按 deferrable 约束的方式声明。ALTER TABLE b ADD CONSTRAINT b_a_fk FOREIGN KEY (a_id) REFERENCES a(id) DEFERRABLE INITIALLY DEFERRED; ALTER TABLE a ADD CONSTRAINT a_b_fk FOREIGN KEY (b_id) REFERENCES b(id) DEFERRABLE INITIALLY DEFERRED;设置成 DEFERRABLE 可以让循环插入的场景更容易绕过约束顺序问题这也是 PG 比 MySQL 在约束设计上灵活一点的地方。6.5 字符集不一致导致 emoji 变成问号这次迁移开始前我在源库跑抽查 SQL 发现一个字段有 emoji幸好连接串里加了字符集参数否则这批数据大概率变成问号。MySQL 5.7 的连接默认字符集不一定和表字符集一致。如果库表是 utf8mb4但连接串没指定pgloader 读取时可能按 utf8 解析四字节 emoji 直接损坏。在源库连接串显式指定字符集FROM mysql://migrator:migpassmysql-src:3306/appdb?charsetutf8mb4迁移完成后抽查含特殊字符的行比如查找表情符号、中文生僻字确保目标库的编码也配置正确。PG 侧通常用 UTF8 就没有问题但如果目标库初始化时不是 UTF8也会在写数据时报编码错误。6.6 JSON 类型的行为差异比想象中大MySQL 5.7 的 JSON 本质是一个带合法性校验的 LONGTEXT存储的还是原始 JSON 文本。PG 的 JSONB 是二进制存储会解析 JSON 内容并做规范化处理比如键的顺序会被重排、重复键只保留最后一个、数字格式会重新编码。迁移本身不会报错但应用行为会有微妙差异。例如某个接口依赖 MySQL 里 JSON 字段的原始文本顺序切到 PG 后返回的 JSONB 文本顺序变了前端展示字段顺序不同虽然不影响解析但如果有做字符串签名或比对就会出问题。处理思路取决于应用依赖的是原始文本还是结构化访问。前者用 PG 的 json 类型保留原始文本后者直接 jsonb。我的默认选择是 jsonb因为它的查询性能更好但对那些有特殊需求的表我会单独 CAST 成 json 类型。7. 大表迁移的性能调优实践7.1 workers、concurrency、rows per range 怎么配pgloader 的默认参数在小库上表现不错但上了大表必须调。我给一套经过验证的推荐值参数小库10GB大库100GB 以上workers4-816-32concurrency12rows per range50000100000batch size50MB100MBprefetch rows10002000workers 不是越大越好。它代表并行处理的表数量如果表本身很大而且源库服务器的磁盘 IO 已经接近瓶颈调高 workers 反而可能拖慢整体速度。我一般从 8 起步观察源库 IO 使用率再调整。concurrency 代表并行写目标库的线程数。机械硬盘上建议保持 1SSD 上可以到 2-4。目标库的写入压力也是考量因素PG 在大量写入时会产生 WAL 和 checkpoint 开销并发太高会让整机 IO 打满。rows per range 控制在 50000-100000 之间比较合理。这个值太小pgloader 会频繁切换数据分块通信和调度开销变大太大则单块处理时间过长中途失败要重来更大范围。7.2 表结构和主键质量直接影响读取速度性能瓶颈不止在 pgloader 一侧源库的表结构影响同样大。有主键的表pgloader 会按主键范围切块每块读取就是一次高效的 range scan。没有主键的表只能全表扫单张 5000 万行的无主键表迁移耗时可能是有主键表的 3-5 倍。如果源库有特别大的无主键表我建议在迁移窗口内先在源库临时补一个自增主键迁移完再删掉。这个操作要和业务方确认避免影响源库写入。更稳妥的做法是直接接受较慢的迁移速度但提前估算好窗口。7.3 目标库参数临时调整一次性导入场景下目标库的 WAL 和 checkpoint 配置值得临时调大。默认配置下PG 在大量写入时频繁触发 checkpoint磁盘 IO 会被反复打满。我习惯在迁移前对目标库执行ALTER SYSTEM SET max_wal_size 10GB; ALTER SYSTEM SET checkpoint_timeout 30min;注意这是临时优化迁移完成后要把配置改回去或者直接把目标实例重建。长期使用过大的 max_wal_size 会让崩溃恢复变慢不适合生产常态。我不建议为了追求速度关掉 fsync那会让数据库在异常断电时面临不可恢复的风险收益也有限。7.4 停机迁移窗口内的最终一致性控制如果业务允许短时间只读整个迁移流程可以设计成两段式。第一次先跑一次全量迁移把绝大部分数据搬过去这个过程不需要停写。等到切换窗口正式开始让应用进入只读维护模式然后对增量期间有变化的表做第二次增量补偿。MySQL 侧可以从 binlog 拿到增量变更但 pgloader 不会自动消费 binlog所以这个过程通常用一个简单的定时任务记录第一次全量迁移的结束时间然后在维护窗口内把该时间点之后变更的行手动同步过去。表如果按 update_time 或者业务主键有版本号字段实现起来就很简单。如果业务数据的变更没有时间戳也没有版本号那就只能拉长维护窗口直接停写后跑完整全量迁移。数据量在几十 GB 级别时pgloader 的迁移时间通常在几十分钟内能完成这个停机时间多数业务是可以接受的。我自己的习惯是把这套流程整理成一个固定脚本数据库名和连接信息通过环境变量传入之后每次接到 MySQL 迁 PG 的需求直接复用。第一次做迁移的同学建议先挑一张中小表练手把 pgloader 的日志输出和类型映射看熟再跑全库顺序千万别反过来。时间字段、布尔字段、索引长度这三个坑提前确认能省下大量返工时间。
阅读完成 · 觉得有帮助?
咨询建站