停写没停干净就切库:MySQL/PG 割接窗口的位点判定、追平阈值与回滚预算
割接方案在窗口开始前就该定完:走全量还是增量、一次切还是分批,这些都轮不到窗口里再讨论。窗口内只剩四件事:确认源端真的没有业务写入、判断增量链路是否追到最后一个事务、把反向链路立起来、在超时前决定继续还是回滚。下面按这四件事的顺序写,命令和阈值给到可直接抄的程度,并标出哪些是云厂商文档写死的口径、哪些必须自己在环境里实测。
一、什么时候允许停写:先把延迟阈值跑满
华为云 DRS 的文档口径是:任务进入「增量运行中」,延迟时间小于 5 秒,并且保持 5 分钟以上,才可以暂停业务写入。腾讯云 DTS 的口径是:任务显示为同步增量,目标与源库数据差距 0KB、时间延迟 0 秒,再停源库业务。
两个阈值量的不是一回事。DRS 看链路延迟时间,DTS 同时看字节差距和秒级延迟。停写之前至少让其中一个条件稳定跑满一个观察期,别在延迟刚跳到 0 的那一秒就动手——那可能只是这一批事务刚好消费完,下一批还没来。
另外,AWS 的割接指引里提到的「锁源环境」是另一条路:在数据库层加锁,从机制上保证源端不再产生新事务。代价是停机时间被锁的时长拉长。要不要用,取决于你对「应用侧停写不可靠」这件事的容忍度。
二、停写:MySQL 的 read_only 挡不住 SUPER 账号
设置只读是第一步,但 read_only=ON 只对没有 SUPER 权限的账号生效。运维账号、监控账号、某些用了高权限连接的旧服务,照样能写进去。MySQL 8.0 上可以再叠一层:
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON;
SHOW VARIABLES LIKE '%read_only%';
设完必须回查一次,确认两个变量都是 ON。super_read_only 会连带把 read_only 顶上去;回滚时顺序反了会报错,要先 SET GLOBAL super_read_only = OFF,再 SET GLOBAL read_only = OFF。
PostgreSQL 没有对应的单开关。可行做法是收权限(REVOKE CONNECT 或改 pg_hba.conf 后 reload),再对残留会话执行 pg_terminate_backend。ALTER SYSTEM SET default_transaction_read_only = on 只影响新会话,而且连接用户自己就能改回来,不能单独当停写手段用。
停写动作本身要在业务低峰期做,这条在 DRS 和 DTS 的文档里都写了。
三、位点:两次 show master status 之间要抽查 event
停写之后,登录源库反复查位点:
SHOW MASTER STATUS;
至少查两到三次,间隔十几秒到几十秒,比对 Executed_Gtid_Set 以及 File / Position 是否还在变。位点不动,说明没有新事务提交。
问题在于位点经常一直在动。心跳表、监控探针、定时任务的空转写入,都会持续产生 binlog。这时要靠抽 event 内容判断是不是业务写:
SHOW BINLOG EVENTS IN 'mysql-bin.000123' FROM 456789 LIMIT 50;
FROM 后面接上一次 SHOW MASTER STATUS 记下的 Position。看事件里的表名和语句:写的是 heartbeat、monitor_* 这类表,可以排除;命中业务表就是没停干净,回去查是谁在写。
Gtid 模式下更省事的判法是等位点稳定后,把源端 @@gtid_executed 带过来,在目标端执行:
SELECT GTID_SUBSET('', @@gtid_executed);
返回 1 表示源端已执行的事务集合全部包含在目标端。这条判据比看秒数可靠,但需要把 GTID 字符串人工搬过去,适合窗口里做一次最终确认。
四、会话:Command=Sleep 的连接不算写入
位点不动不代表会话干净。腾讯云 DTS 文档给的判据是:1~5 分钟内,除迁移实例连接外无任何新会话执行,即可认为业务完全停止。按源库类型查:
-- MySQL
SHOW PROCESSLIST;
-- SQL Server
SELECT * FROM sys.dm_exec_connections;
-- PostgreSQL
SELECT * FROM pg_stat_activity;
-- MongoDB
use admin;
db.runCommand({ currentOp: 1, $all: [{"active": true}] });
-- Redis
CLIENT LIST
Redis 的 CLIENT LIST 在存量连接很多时自己就可能造成卡顿,文档里明确提示谨慎执行。
MySQL 的 SHOW PROCESSLIST 要看三列:Command、State、Info。Command=Sleep 的连接是空闲连接,不代表正在写入,也不代表没有未提交事务。真正要看的是有没有处于执行中的语句,以及 Time 这一列——它是连接处于当前状态的时间,不是事务持续时间。要判断长事务,直接查:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx;
trx_state=RUNNING 的长事务必须先处理:要么等它跑完,要么 kill 掉对应线程后重做业务侧确认。长事务不结束,binlog 位点可能已经追平,但数据页上的修改还没提交,此时的「追平」是假象。事务提交那一刻位点会再跳一次,链路又落后一截。
PostgreSQL 侧对应的是 pg_stat_activity 里 state 非 idle 的行,重点看 state = 'idle in transaction' 和 query_start 距当前多久。
五、追平:复制延迟 0 不等于数据一致
这一条最容易在窗口里出错。Seconds_Behind_Source(MySQL 8.0.22 之前的名字是 Seconds_Behind_Master)为 0,只说明 SQL 线程追上了 IO 线程当时读到的位置,不代表目标端已经拿到源端最后一个事务。几种典型情况:
大事务在源端跑了十分钟才提交,binlog 只在提交时一次落盘,目标端在此之前延迟显示一直是 0;并行复制下 worker 线程各自推进,协调线程的位点可能领先于实际执行完的最小位点;relay log 还没执行完,Retrieved_Gtid_Set 已经等于源端。
窗口里可用的判据:
- GTID 子集判断,同上。
- 对比
SHOW REPLICA STATUS里的Retrieved_Gtid_Set与Executed_Gtid_Set是否都等于源端的@@gtid_executed。两者都要对,只看一个不够。 - 用 pt-heartbeat 独立于复制状态量延迟:
pt-heartbeat --update -D heartbeat -h --interval=1
pt-heartbeat --monitor -D heartbeat -h
心跳表写入源端,目标端读时间戳差。它绕开了复制线程自报的延迟,能反映真实数据滞后。这条需要提前部署,窗口里现搭来不及。
业务层的最后一道是数据对比:DTS 建议创建数据对比任务,内容对比耗时长时改行对比,或挑核心表配查询条件抽检。DRS 同样建议创建数据对比任务。行数加关键字段校验和,是窗口里能实际跑完的粒度。
六、反向链路:提前建好,割接后立刻起
DTS 的做法是正反向两个独立的单向同步任务。反向任务在正向同步阶段就配置好,只选增量、初始化类型都不勾选、已存在同名表选「忽略并继续执行」,配完先不启动。正向同步完成后停正向任务、业务接目标库,随即启动反向任务,把目标库的增量写回源库。之后如果目标库数据异常,停反向、业务切回源库。
启动反向任务前必须确认两端的位点或 GTID:反向的起点应当正好是目标库接受业务写入的位置,起早了会重复应用,起晚了中间那段增量就丢了。同时不要出现正反向同时运行的情况,双向回环会把同一条变更反复放大。
DRS 的差别在于任务类型:实时灾备任务可以直接倒换;实时同步、实时迁移任务要暂停或结束。暂停任务不会迁移 event、trigger 这类对象,需要一并迁移就得结束任务再建反向。这条在割接单上要单独标出来。
七、回滚预算:在非生产环境实测一次
AWS 把回滚分成两档:割接后目标端还没产生新数据,改路由回本地实例即可;已经产生新数据,就得把数据从目标端恢复回源端,可选方案是 fail-forward、双写、或本机备份还原。回滚计划里要写清三件事:触发检查点是什么、回滚走哪条策略、谁有权决定是继续向前修还是回滚。
这里的 RTO 不能拍脑袋。窗口里留多久给回滚,取决于你在非生产环境实测过一次的数:备份还原花了多少分钟、反向链路从启动到追平花了多少分钟。这个数写进割接单,到点没追上就按预案走,别在窗口里现场估算。
割接窗口检查清单
按分钟计的操作顺序,阈值和命令对应上文各节:
- T-30min:确认增量链路延迟 <5s 已持续 5 分钟(DRS),或差距 0KB、延迟 0s(DTS);跑一轮核心表行对比或抽检。
- T-10min:通知业务方停止批量任务与 DDL,确认没有 gh-ost / pt-osc 变更在跑;记录源端
SHOW MASTER STATUS的File/Position/Executed_Gtid_Set。 - T-0:停写。MySQL 设
read_only+super_read_only并回查SHOW VARIABLES LIKE '%read_only%';PG 收连接权限并pg_terminate_backend。 - T+1min:再查两次
SHOW MASTER STATUS比对位点;位点仍在动就用SHOW BINLOG EVENTS IN 'xxx' FROM yyy抽查 event,排除心跳与监控写入。 - T+1~5min:用
SHOW PROCESSLIST/pg_stat_activity/sys.dm_exec_connections/currentOp/CLIENT LIST确认除迁移实例外 1~5 分钟无新会话;innodb_trx里有trx_state=RUNNING的长事务,先 kill 或等其结束。 - T+5min:判定追平。GTID 子集返回 1、
Retrieved_Gtid_Set与Executed_Gtid_Set均对齐,或 pt-heartbeat 延迟归零;不满足就继续等。 - T+6min:结束正向任务(需要迁 event/trigger 时必须结束而非暂停),立即启动已配置好的反向增量任务,确认起点位点等于目标库当前写入位置。
- T+7min:业务连目标库、恢复写入、做业务验证;同时盯住回滚预算剩余时间,到点未达标按割接单写明的策略执行,不再现场讨论。