在产品迭代初期或者系统重构时期, 业务模型的调整带来数据结构的变化, 数据迁移不可避免. 做好数据迁移需要考虑周全, 且准备充分, 做好预案, 否则如果出现数据不一致问题, 纠错成本高, 同时核心业务数据的错误, 会引起客户 / 业务方的投诉, 团队也会承受巨大的压力.
本文结合最近一个实际项目的数据数据迁移过程, 讲述了踩过的坑, 加上自己的一些思考得出的一些方法论, 最后给出了数据迁移个脚本的一个实例
目标
确保新客户端访问新业务模型时能否正常查询之前的数据, 如订单等; 不会出现数据不一致.
原则
影响可控 -- 只对需要迁移对数据做修改, 不能影响到其他数据; 不到万不得已, 不会允许停机迁移数据, 因此迁移窗口期越短越好, 减少迁移窗口期用户行为带来的数据问题;
可回退 -- 一旦发现迁移数据有问题, 可以回退到之前的数据状态;
可追溯 -- 出现问题, 能够有日志或者备份数据可查; 数据库的 binlog, 迁移程序的 log 可以作为依据;
可测试 -- 迁移方案必须可测试, 要满足可测试, 那么迁移方案必须是通用型的方案.
思路
先备份, 再迁移;
迁移后需要做数据比对, 确保数据一致性;
出现问题, 考虑是否做回退 [并不是所有场景都能直接回退] ;
如果业务量大, 为避免用户行为和数据迁移产生冲突, 考虑停服务迁移.
步骤
备份 -- 将待迁移数据备份到 bak 表, 任何在迁移过程中会被修改的数据应当被备份, 任何在 insert 场景被当着原数据使用的数据应当被备份;
迁移 -- 迁移脚本 / 程序 只对 bak 表中的目标数据做操作;
验证 -- 迁移完成后, 需要做数据核对, 确保数据一致性;
回退 -- 回退脚本同样只对 bak 表中的目标数据做操作, 且行为和迁移脚本行为相反.
方案的选择
数据库脚本
在数据量小, 业务场景简单的情况下非常适合, 简单轻量级, 通过 sql 脚本完成数据迁移非常合适. 但如下几点需要认真思考:
能否写成通用 sql? 如果不能放弃; 因为为不同环境准备不同的脚本, 破坏了'可测试'这一原则;
被操作的数据量是否太多? 如果过多 (通常超过 1 万条就很多了), 可能导致脚本提交超时;
业务场景是否复杂? 如果涉及到的表过多 (超过 3 张), 脚本的执行存在先后顺序, 这时候通过人为保证, 风险会大大增加;
测试环境和线上环境的 sql 执行工具 / 环境是否一致? 如果不一致 (很多公司的 DBA 工具会对一些语法和格式作出限制, 比如不能有换行, 注释中不能有半角分号等), 则也会破坏掉'可测试'这一原则;
线上 sql 执行流程是否冗长? 如果流程冗长 (公司的流程可能要求需要 TL 和 DBA 的审批, DBA 作为第三方资源依赖不可控), 且脚本多, 会拉长数据迁移的窗口期, 业务风险大大增加, 破坏了'影响可控'的原则.
迁移程序
和'数据库脚本'方法相反, 撰写的'迁移程序'能够避开这些缺点, 更适合于业务场景复杂, 数据量大的场景.
方案对比
项 | 脚本 | 程序 | 备注 |
---|---|---|---|
通用性 | 不完全 | 完全 | 如:依赖第三方数据时,脚本无法做到通用 |
复杂场景支持 | 不适合 | 适合 | 复杂业务场景脚本不适合,如循环调用,第三方系统数据,多表依赖等 |
大数据量支持 | 不适合 | 适合 | 大数据量可能导致脚本提交超时,通常超过 1 万条不宜使用脚本,大多数 dba 工具通常也会对操作的数据量做限制 |
开发成本随复杂度增长 | 指数 | 线性 |
无论采用'数据库脚本'还是'迁移程序', 都需要遵循上面的'原则'和步骤.
思考
数据迁移和应用程序发布的先后顺序?
在业务量大的情况下, 数据迁移过程中, 数据被用户行为修改了怎么办?
迁移失败, 什么情况下做回退, 什么情况下不能做回退?
踩过的坑
在转赠 2.0 项目中, 前期对数据迁移的业务复杂度预估不足, 选择了'数据库脚本'方式, 踩了不少坑:
在测试环境测试通过的 sql 脚本, 无法直接在线上环境执行, 原因是线上数据库工具对 sql 格式和内容校验更为严格 (不允许有换行等);
测试过程中发现, 需要实现为一个订单循环生成多个券码的场景, 虽通过 sql 间接实现, 但是复杂度很高, 开发成本增大;
由于 sql 较多且复杂度高 (如: 使用 insert select), 需要经过 TL 和 DBA 的双重审批, 拉长了数据迁移窗口.
迁移脚本示例
业务场景: 将订单表中订单状态为 2 和 3 的订单状态更新为 4
- backup
- create table order_bak like order;
- insert into order_bak select * from order where status in (2,3);
注: order 为业务表, order_bak 为备份表; 目标是将订单状态为 3 的订单更新为 4.
- migration
- update order set status = 4 where id in (select id from order_bak);
注: 如果直接对 order 原表进行操作, 一旦错误, 无法回滚.
- check
- select count(*) as cont from order where status != 4 and id in (select id from order_bak);
注: 如果 cont> 1 则需要考虑数据迁移是否成功
- rollbak
- update order o inner join order_bak bak on o.id = bak.id set o.status = bak.status;
注: 如果迁移后 --> 回滚前, 有用户行为更改 status 状态, 则不能直接 rollback , 需要具体情况具体分析; 实在避免不了, 且此类数据很多, 则考虑停服务迁移了.
来源: https://www.cnblogs.com/daoqidelv/p/9594910.html