全部产品
云市场

不锁表结构变更附录

更新时间:2020-04-10 16:15:42

DMS无锁结构变更的目标是要解决所有结构变更中的锁表问题,避免因为锁表导致业务不可用甚至故障。
以MySQL为例,在MySQL5.6之前还没有OnlineDDL解决方案时,大部分表的ALTER操作都要涉及到Lock > copytonewtable > rename > unlock这个过程,表越大锁表时间越长,所以业务上修改一张表都是非常困难的事情。
从MySQL5.6开始,官方提供了OnlineDDL的特性可以做到对大部分的ALTER不锁表,但是添加/删除列、修改列类型等操作依然需要重建来完成。
大表做结构变更就没有其他的方案了吗?

变更方案对比

线上业务在部分DDL情况下不建议直接操作DDL。通常有几种做法来避免影响业务:

  • 业务低峰期去变更。

    合适的变更窗口以及窗口长度直接影响变更结果。过大的表由于在变更窗口内未执行完成,仍将影响业务。

  • 备库修改后切换主备。

    需要存在主备实例,并能够在合适窗口进行主备切换。

  • Online修改工具,如pt-online-schema-changeOnlineSchemaChangegh-ost

    这三种工具在alter表时不需要锁定表,所以不会阻塞读写,使用外部干预的方式完成表的变更。

    • pt-online-schema-change:工作原理是创建和源表A一样的表A_gst执行DDL操作,同时在A上创建一个DML触发器,然后将A中的数据拷贝到A_gst,在拷贝过程中产生的增量变更就用触发器完成同步更新。拷贝结束后执行两张表的rename操作完成变更。
    • OnlineSchemaChange:工作原理和pt-online-schema-change基本一致,不同的地方是它采用的是异步模式,在A_gst的基础上创建了一张日志表,触发器的条目更新将直接落在日志表中,后台进程将日志表中的条目应用到A_gst表。这样整个流程上是异步的,也能够控制回放速度。
    • gh-ost:与上面两种变更流程基本一致,但是没有使用触发器的设计,所以增量变更的数据来源不是触发器,而是Binlog文件。订阅读取该文件中A表的变更记录,将记录解析并应用到A_gst表。这样的数据对于gst表回放非常有利,binlog中存储的都是A表的记录,易于直接读取和应用。

触发器存在的问题

基于触发器设计的工具代码逻辑相对简单,大部分数据上的工作交给了触发器去完成,包含数据库的隐式处理、数据类型以及切换等相关操作都在触发器的单元中完成,简化了进行实时表迁移的大量流程。但同样的存在以下几个问题:

  • 数据库开销

    触发器是一个存储过程,随着业务的DML,触发器的执行必然存在开销,业务繁忙时更甚。

  • 触发器将两张表的操作关联到一个事务空间中,所以锁的竞争会增加,即一个事务中的两张表锁并集。触发器的设计中拷贝数据和变更数据只能并行,无疑将会增加锁竞争。

  • 异常处理

    触发器的设计,意味着触发器永远保持运行无法暂停。当服务器繁忙、主备延迟、异常等情况时,在变更流程中的任何一个阶段都无法取消触发器,强行取消将导致变更中断或数据丢失,从而导致A_gst表数据不准确。虽然上述两个产品均有限流的概念,但是基于触发器设计的节流只是部分性的,比如在拷贝表阶段,可以暂停拷贝或减缓拷贝速度,但是触发器仍在运行并占用开销。

  • 可靠性测试

    在验证方案上我们期望得到任务的预期时间等信息,在备库上创建触发器并模拟,前提需要在statement模式下。ROW模式下无法模拟,因为在主库上的触发器产生的数据效果重放到了备库上。另外一方面,即使是statement模式,MySQL的回放是单线程的,statement的单线程执行无法模拟、复现主库上的并发场景,也就无法验证和测试并发和锁相关的问题。

无触发器设计

无触发器设计最大的优点就是和数据库的工作负载解耦。触发器的设计无论何种情况下,源表的DML都会同时在另外一张表上同步操作。非触发器设计将这个过程解耦,即新表的写入和源表的写入不存在直接依赖。这样的流程会完美解决触发器设计带来的几个问题:

  • 数据库开销

    作为一个伪装的SLAVE订阅主、备的Binlog事件,将其中的源表事件过滤下来并回放到目标表。这个过程和源表的变更没有任何关系,也不需要数据库上任何存储过程等干涉这个写入,触发器开销占用的问题不存在。

  • 上面提到方案解耦了源表和目标表的依赖,所以锁竞争也就不复存在。关于目标表上的拷贝和更新时的竞争,我们在逻辑上使用交叉执行的方式避免和降低锁竞争,虽然会影响变更效率,但是很显然降低了数据库负载。

  • 异常处理

    仍然是解耦带来的好处,订阅Binlog的线程随时可以暂停或者放慢速度,在系统繁忙和主备延迟较大时对工作中的应用开启节流,避免问题扩大。

  • 可靠性

    基于Binlog的另外一个好处是,在主库和备库上操作Online没有任何区别。这样避免对线上业务的干扰或资源争用。另外一方面,通过在备库上模拟操作变更,最终并不实际切换源表和目标表,然后对源表和目标做校验来持续验证可靠性。当然,没有所有场景下的完美方案,无触发器的设计存在下面的问题。

无触发器存在的问题

  • 代码复杂性

    在触发器设计的描述中提到,它主要依赖触发器的同步和数据库内部操作,工具的作用相对较小。非触发器的设计基于binlog,有很大的自由度,但是复杂度会大幅增加。需要注册为一个SLAVE、订阅事件并转为SQL重新写入,异常处理相对简单的如处理连接失败、复制延迟以及数据类型等,其他程序的异常诸如程序负载、不可控异常等都要在代码上进行关注。同时逻辑中需要包含大量的代码以及更复杂的并发控制逻辑。

  • 网络流量

    相比触发器在数据库的内部处理,非触发器方案需要订阅事件流以及回写数据,这将使用到主机间的流量,占用MySQL的进程流量。代码的复杂性依赖缜密的算法逻辑,完善的测试用例集来保证健壮性和稳定性。但是相比之下,它带来了更多的好处,比如可以指定时间切表、拷贝或者增量流量控制等一些额外的功能。

结语

锁表变更带来的影响对业务甚至是致命的,通过引入非触发器的方式可以解决大表无锁变更这个难题。它并不完美,但是带来的好处是显而易见的。