做中考试卷的网站,seo做的比较好的网站的几个特征,网站在线备案,开发一个app的费用是多少数据迁移方面的工作#xff1a;
重构老系统#xff1a;使用新的表结构来存储数据单库拆分分库分表、分库分表扩容大表修改表结构定义
数据备份工具
MySQL上常用的两款数据备份工具#xff1a;mysqldump和XtraBackup
mysqldump#xff1a;一个用于备份和恢复数据库的命令…数据迁移方面的工作
重构老系统使用新的表结构来存储数据单库拆分分库分表、分库分表扩容大表修改表结构定义
数据备份工具
MySQL上常用的两款数据备份工具mysqldump和XtraBackup
mysqldump一个用于备份和恢复数据库的命令行工具允许用户导出MySQL数据库的结构、数据及与表之间的关系以便在数据库发生问题时进行恢复。是一个逻辑备份工具导出的内容是一条条SQL XtraBackup使用了InnoDB存储引擎的数据备份技术支持增量备份和恢复并且支持多主机备份和恢复。是一个物理备份工具相当于直接复制InnoDB的底层存储文件。 如果你使用的不是 MySQL可以自己收集一下你使用的数据库的工具。要注意分析这些工具的优缺点尤其是导入导出速度以及可行的优化手段。
innodb_autoinc_lock_mode
innodb_autoinc_lock_mode是InnoDB引擎里面控制自增主键生成策略的参数有三个取值
0使用表自增键锁但是锁在INSERT语句结束之后就释放了1使用表自增锁如果是普通的INSERT INTO VALUE 或者 INSERT INTO VALUES 语句申请了主键就释放锁而不是整个INSERT语句执行完毕才释放。如果是INSERT SELECT 等语句因为无法确定究竟要插入多少行所以都是整个INSERT语句执行完毕才释放。2使用表自增锁所有的语句都是申请了主键就立刻释放 在执行 INSERT INTO VALUES 的时候不管插入多少行都只申请一次主键一次申请够这些主键必然是连续的。所以你可以从返回的最后一个 ID 推测出全部 ID 面试准备
innodb_autoinc_lock_mode的值这回影响主键生成策略在数据迁移的时候需要考虑处理主键问题公司的binlog模式在后面增量校验和修复数据里面使用的是行模式的binlog公司是否有统一的数据库规范比如必须要更新时间戳、不能硬删除只能软删除使用的ORM框架怎么实现双写公司是否做过数据迁移如果做过具体的方案是什么
重构系统 这个系统是我们公司的一个核心系统但是又有非常悠久的历史。在我刚接手的时候它已经处于无法维护的边缘了。但是不管是重构这个系统还是重新写一个类似的系统已有的数据都是不能丢的。所以我的核心任务就是重新设计表结构并且完成数据迁移。为此我设计了一个高效、稳定的数据迁移方案。 实际落地了单库拆分 分库分表 进公司的时候刚好遇上单库拆分分库分表。我主要负责的事情就是设计一个数据迁移方案把数据从单库迁移到分库分表上。 解决方案
数据迁移方案的基本步骤
创建目标表用源表数据初始化目标表执行一次校验并修复数据此时用源表数据修复目标表数据业务代码开启双写读源表并且先写源表数据以源表为主开启增量校验和数据修复保持一段时间切换双写顺序此时读目标表并且先写目标表数据以目标表为准继续保持增量校验和数据修复切换为目标表单写读写都只操作目标表
不考虑数据校验的话整个数据迁移过程是这样的 图右边的两步都要考虑校验和修复数据的问题
初始化目标表数据
在创建了一个目标表之后第一步是先尝试初始化目标数据问题是你怎么拿到源表数据
基本思路有两个
使用源表的历史备份源表导出数据
以mysqldump为例 关键词是加快导入和导出速度
我选择了从源表导出数据使用的是 mysqldump 工具。mysqldump 是一个开源的逻辑备份工具优点是使用简单能够直接导出整个数据库。缺点则是导出和导入的速度都比较慢尤其是在数据量非常大的情况下。 所以我针对 mysqldump 做了一些优化来提高导出和导入的性能。 加快导出速度能做的事情并不多主要就是开启 extended-insert 选项将多行合并为一个 INSERT 语句。 加快导入速度就可以做比较多的事情。
关闭唯一性检查和外键检查源表已经保证了这两项所以目标表并不需要检查。关闭 binlog毕竟导入数据用不着 binlog。调整 redo log 的刷盘时机把 innodb_flush_log_at_trx_commit 设置为 0。 注意在第 2、3 点里面可以把话题引向的 binlog 和 redolog 内容。反过来你也可以利用 binlog 和 redolog 把话题引导到这一节课的内容灵活应对就可以。 第一次校验与修复
在初始化数据之后可以先尝试立刻校验和修复一下数据因为如果你前面用的是备份数据那么备份数据已经落后生产数据了。 比如你用的是昨天的备份那么今天的修改目标表就没有。还有如果你是导出的数据那么导出数据到你导入数据的这段时间数据发生了变化目标表依旧是没有的。 如果你们公司有明确的数据库规范的话比如说所有的表都需要有update_time这个字段那么校验和修复的时候就可以采用增量的方案。因为只有update_time晚于你导出数据的时间点才说明这一行的数据已经发生了变更。在修复的时候就直接用源表的数据覆盖掉目标表的数据。
业务开启双写以源表为准
首先要解释清楚是怎么做到双写的。 支持双写大体上有两个方向侵入式和非侵入式两种。 侵入式方案就是直接修改业务代码要求业务代码在写了源表之后再写目标表。但是侵入式方案是不太可行的或者说代价很高。因为这意味着所有的业务都要检查一遍然后修改。既然修改了自然还要测试。所以一句话总结就是工作量大还容易出错。
非侵入式一般和你使用的数据库中间件有关比如ORM框架。这一类框架一般会提供两种方式来帮你解决类似的问题。
AOPAspect Oriented Program 面向切面编程方案不同框架有不同叫法比如说可能叫做interceptor,middleware,hook,handler,filter。这个方案的关键是捕捉到发起的增删改调用篡改为双写模式。 数据库抽象操作可能叫做Session, Connection, Connection Pool, Executor等就是将源表的操作修改为双写模式。
不管用哪种方案都是确保一个东西就是双写可以在运行期间随时切换状态单写源表、先写源表、先写目标表、单写目标表都可以。 大多数时候都是利用一个标记位然后你可以通过配置中心或者接口直接修改它的值。 以GORM为例 开启双写在 GORM 上面还是比较容易做到的。最开始我觉得可以考虑使用 GORM 的 Hook 机制用 DELETE 和 SAVE 两个 Hook 就可以了。但是这要求我必须给每一个模型或表都定义类似的 Hook还是比较麻烦的。后来我仔细翻了 GORM 的文档确认可以考虑使用 GORM 的 ConnPool 接口。所以我用装饰器模式封装了两个数据源每次执行语句的时候都根据标记位来执行双写逻辑。 func (m *DouleWritePool) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {if m.mode 源表优先 {err m.source.QueryContext(ctx, query, args...)if err nil {m.target.QueryContext(ctx, query, args...)}} else if {//...}
}在双写的时候可以往两个方向进一步刷亮点数据一致性问题和主键问题
数据一致性问题
正常面试官都可能会问到如果在双写过程中写入源表成功了但是写入目标表失败了该怎么办 最基础的回答就是不管 写入源表成功但是写入目标表失败这个是可以不管的。因为我后面有数据校验和修复机制还有增量校验和修复机制都可以发现这个问题。 然后可以提出一个曾经思考过但是最终没有实施的方案这能够证明你在数据一致性上有过很深的思考。关键词是难以确定被影响的行。 在设计方案的时候我考虑过在写入目标表失败的时候发一个消息到消息队列然后尝试修复数据。但是这个其实很难做到因为我不知道该修复哪些数据。比如一个UPDATE语句在目标表上执行失败我没办法根据UPDATE语句推断出源表上哪些行被影响到了。 主键问题
如果在源表中使用的是自增主键那么在双写的时候写入目标表要不要写入主键答案是要的。 需要在写入源表的时候拿到自增主键然后在写入目标表的时候设置好主键。因为其实你并不能确保目标表自增的主键和源表自增的主键是同一个值。比如在并发场景下两次插入。 因此你可以介绍你是如何处理这个问题的 在双写的时候比较难以处理的问题是自增主键问题。为了保持源表和目标表的数据完全一致需要在源表插入的时候拿到自增主键的值然后用这个值作为目标表插入的主键。 此外还可以进一步展示一个更加高级的亮点也就是我在前置知识里说到的innodb_autoinc_lock_mode 取值会影响到自增主键的连续性抓住关键词 自增主键的连续性 在处理批量插入的时候更要小心一些。正常来说批量插入如果使用的是VALUES语法那么生成的主键是连续的可以从返回的最后一个主键推测出前面其他行的主键。即使innodb_autoinc_lock_mode 的取值是2也能保证这一点。但是如果是多个INSERT INTO VALUE语句或者INSERT SELECT语句这些语句生成的主键就可能不连续。在双写之前都要先改造这一类的业务。 增量校验和数据修复
增量校验基本就是一边保持双写一边校验最新修改的数据如果不一致就要进行修复。这里有两个方案。第一个方案是利用更新时间戳比如说update_time这种列第二个方案是利用binlog。相比之下binlog更加高级在面试的时候应该优先考虑这个方案。
利用更新时间戳
利用更新时间戳的思路很简单就是定时查询每一张表然后根据更新时间戳来判断某一行数据有没有发生变化。
for {// 执行查询// SELECT * FROM xx WHERE update_time last_timerows : findUpdatedRows()for row in rows {// 找到目标行要用主键来找用唯一索引也可以看你支持到什么程度tgtRow findTgt(row.id)if row ! tgtRow {// 修复数据fix()}}// 用这一批数据里面最大的更新时间戳作为下一次的起始时间戳last_time maxUpdateTime(row)// 睡眠一下sleep(1s)
}所以可以介绍基本的策略关键词是更新时间戳 我们采用的方案是利用更新时间戳找出最近更新过的记录然后再去目标表里面找到对应的数据如果两者不相等就用源表的数据去修复目标表的数据。这个方案有两个条件所有的表都是更新时间戳的并且删除是软删除的。 这里提到的第二个条件就是准备展示的第一个亮点你可以等面试官追问为什么必须是软删除也可以自己接着回答。 如果不是软删除那么源表删掉数据之后如果目标表没删除在我们的匹配逻辑里是找不到的。在这种场景下还有一个补救措施就是反向全量校验。也就是说从目标表里再次查询全量数据再去源表里找对应的数据如果源表里没找到就说明源表已经删了数据那么目标表就是可以删除数据了 接下来可以进一步展示第二个亮点主从同步延迟引发的问题。假设你在校验和修复的时候读的是从库那么会遇到两种异常情况一种是目标表主从延迟另一种是源表主从延迟。 那么怎么解决呢简单粗暴的方法就是全部读主库校验和修复都以主库数据为准。缺点是对主库的压力比较大。
更加高级的方案双重校验 校验和修复的时候要小心主从同步的问题如果校验和修复都是用从库的话那么就会出现校验出错或者修复出错的情况。按照道理来说强制走主库就可以解决问题但是这样对主库的压力比较大。 所以我采用的是双重校验方案第一次校验的时候读从库如果发现数据不一致再读主库用主库的数据再校验一次。修复的时候就只能以主库数据为准。这种方案的基本前提是主从延迟和数据不一致的情况是小概率的所以最终会走到主库也是小概率的。 在这个回答里面你没有提到任何异常情况的具体场景如果面试官问到了就回答上面图里面的两种情况。这个亮点能够凸显你在主从延迟方面的积累也会把话题引到主从模式和主从同步上所以要做好准备。
利用binlog
binlog是一个更加高级的方案它还有一些变种所以理解和记忆的难度也更高一些。这里的binlog是基于行的binlog模式。 最简单的形态就是把binlog当作一个触发器回答的关键词就是binlog触发。 在校验和修复数据的时候采用的是监听binlog的方案。binlog只用于触发校验和修复这个动作当我收到binlog之后会用binlog中的主键去查询源表和目标表再比较两者的数据。如果不一致就用源表的数据去修复目标表。 如果更进一步会觉得binlog里面本来就有数据那么干嘛不直接用binlog里的数据所以就有了第二个形态binlog的数据被看作源表数据。 拿到binlog之后我用主键去目标表查询数据然后把binlog里面的内容和目标表的数据进行比较。如果数据不一致再用binlog里的主键去源表里查询到数据直接覆盖目标表的数据。 这里有一点不同就是发现不一样之后需要查询源表再用查询到的数据去覆盖目标表的数据。因为你要防止binlog是很古老的数据而目标表是更加新的数据这种情况。
紧接着会发现目标表也有binlog所以干嘛不直接比较源表的binlog和目标表的binlog如果binlog不一样那不就说明是目标表那边出了问题吗 这种方案理论上是可行的但是它有两个非常棘手的问题。
一次双写可能立刻就收到了源表的 binlog但是你过了好久才收到目标表的 binlog。反过来先收到目标表的 binlog隔了很久才收到源表的 binlog 也一样。所以你需要缓存住一端的 binlog再等待另外一端的 binlog。顺序问题如果有两次双写操作的是同一行那么你可能先收到源表第一次的 binlog再收到目标表第二次双写的 binlog你怎么解决这个问题呢你只能考虑利用消息队列之类的东西给 binlog 排个序确保 binlog 收到的顺序和产生的顺序一致。
虽然能够进一步减轻数据库查询的压力但是实在过于复杂得不偿失。
切换双写顺序
这一步本身就是一个亮点。因为很多人都是在双写的时候直接切换到目标表单写。但是这样直接切换的风险太大了万一出了问题都没法回滚。 所以中间要引入一个双写的时候先写目标表且业务读目标表的步骤这样万一切换到写目标表出了问题还可以回滚。
介绍这一步的时候补充说明一下 引入这一步是为了能够在切换到以目标表为准之前有一个过渡阶段。也就是说通过先写目标表再写源表这种方式万一发现数据迁移出现了问题还可以回滚为先写源表再写目标表确保业务没有问题。 保持增量校验和修复
在切换了双写顺序之后保持增量校验和修复是顺理成章的方案和步骤5一样不过步骤5的校验和修复都是以源表为准那么在这一步就是以目标表为准。
不管什么先后顺序、什么并发问题在修复的时候你永远用主表的最新数据去修复绝对不会出问题如果源表或者目标表本身也是分库分表的那么无非就是查询、修复数据的时候使用对应的分库分表规则而已整个方案在第八步之前都是可以回滚的。一旦切换到第八步就不可能回滚了。