一般意义上的触发器,尤其是基于触发器的表定义修改操作,都有如下问题:
触发器就是存储过程,都是解释型代码,MySQL不会做预编译。把它们硬嵌入到业务操作的事务空间中,会给你要修改的表上执行的每条操作都增加命令分析和解释的开销。
锁:触发器与操作语句分享相同的事务空间,当操作语句释放了原始表上的锁之后,触发器再去释放另一张表上的锁。在同步模式下这样行为的后果尤其严重。主库上的锁竞争与写并发有直接关系。Github在生产环境中曾经遇到过锁竞争导致的几乎乃至完全锁住的情况,完全无法访问表或者整个数据库。触发器导致的另一种锁是在创建或销毁触发器时对元数据的锁。在完成修改表定义之后从比较忙的表上删除触发器时,甚至曾经碰到几十秒甚至几分钟无法提供服务的情况。
无法暂停:当主库业务负载开始增高时,你可能会想要暂停或者取消还没完成的修改表定义的任务。可是基于触发器的方案没办法这么做。也许你可以暂停行拷贝的操作,但却不能暂停触发器,因为把触发器停掉会导致临时表中丢数据。所以,在整个过程中触发器都必须一直处于工作状态。在一些繁忙的服务器上,曾经出现过即使把在线操作全停掉,最后主库还是被触发器给拖死的情况。
并发修改:大家都希望能同时修改多张表的定义。考虑到上面分析的触发器的代价,在生产系统中Github并不敢以触发器的模式同时修改多张表的定义,事实上也没听说有哪家公司真的在线上这么干。
测试:大家也许想测试一下修改方案是否可行,评估一下负载。基于触发器的方案只能在从库上通过基于语句的复制来模拟一下,由于从库上的复制操作是单线程的(即使用了多线程复制的方案,大部分情况下也还是这样的),这样远不能模拟出在主库上修改过程中的真实情况。
gh-ost
gh-ost是gitHub’s Online Schema Transmogrifier/Transfigurator/Transformer/Thingy的缩写,意思是GitHub的在线表定义转换器。
gh-ost有以下特点:
无触发器
轻量级
可暂停
动态可控
可审计
可测试
可靠性高
无触发器
gh-ost不使用触发器,它跟踪二进制日志文件,在对原始表的修改提交之后,用异步方式把这修改内容应用到临时表中去。
gh-ost希望二进制文件使用基于行的日志格式,但这并不表示如果主库上使用的是基于语句的日志格式,就不能用它来在线修改表定义了。事实上,Github常用的方式是用一个从库把日志的语句模式转成行模式,再从这个从库上去读日志。搭一个这样的从库并不复杂。
轻量级
因为不需要使用触发器,gh-ost把修改表定义的负载和正常的业务负载解耦开了。它不需要考虑被修改的表上的并发操作和竞争等,这些在二进制日志中都被序列化了,gh-ost只操作临时表,完全与原始表不相干。事实上,gh-ost也把行拷贝的写操作与二进制日志的写操作序列化了,这样,对主库来说只是有一条连接在顺序的向临时表中不断写入数据,这样的行为与常见的ETL相当不同。
可暂停
因为所有写操作都是gh-ost生成的,而读取二进制文件本身就是一个异步操作,所以在暂停时,gh-ost是完全可以把所有对主库的写操作全都暂停的。暂停就意味着对主库没有写入和更新。不过gh-ost也有一张内部状态跟踪表,即使在暂停状态下也会向那张表中不断写入心跳信息,写入量可以忽略不计。
gh-ost提供了比简单的暂停更多的功能,除了暂停之外还可以做:
负载:与pt-online-schema-change相近的一个功能,用户可以设置MySQL指标的阈值,比如设置Threads_running=30。
复制延迟:gh-ost内置了心跳功能来检查复制延迟。用户可以指定查看哪个从库的延迟,gh-ost默认是直接查看它连上的那个从库。
命令:用户可以写一些命令,根据输出结果来决定要不要开始操作。比如:SELECT HOUR(NOW()) BETWEEN 8 and 17.
上述所有指标即使在修改表定义的过程中也可以动态修改。
标志位文件:生成一个标志位文件,gh-ost就会立刻暂停。删除文件,gh-ost又会恢复工作。