作者:archimekai,转载请注明出处
参考文献:Online Schema Evolution is (Almost) Free for Snapshot Databases (VLDB 2023)
背景
在需求的驱动下,现代数据库应用经常会修改表结构。但是,目前的数据库系统对在线表结构变更的支持仍然不足。这导致用户需要小心规划停机时间才能执行表结构变更,影响业务的可用性。随着持续部署和持续集成的流行,表结构变更发生的频率更为频繁,可以达到一周好几次,这要求在没有DBA介入的情况下实现0停机和鲁棒的错误处理。也就是说,表结构变更需要(1)支持在线执行,不阻塞数据读写(2)支持事务的ACID性质,从而在表结构变更失败时实现安全的回滚,不损坏用户数据。
表结构变更的必要性
在需求的驱动下,现代数据库应用经常会修改表结构,例如增加列,修改约束等。表结构变更一般是通过DDL语句完成的,例如CREATE INDEX, ALTER TABLE等。
数据库系统执行DDL的一般步骤为,首先更新数据库的元信息,以存储演进后的表结构,然后对照新增加的约束检查数据,最后将现存的数据进行修改以满足新的表结构。表结构变更中的数据检查和数据修改会引发大量的数据扫描或移动,从而阻塞并发的DML。
现有技术的不足
过去的工作常常通过在现有系统上挂插件的方式来解决表结构变更的问题,这导致其方案存在一些边角条件(corner case),或者功能不完备。
MySQL: 不支持事务性的DDL,并不是所有的操作都是在线/原子的,并且经常不支持并发的DML。事务中执行DDL会导致之前的操作被静默提交,可能导致隐藏的正确性问题。
一些延迟执行数据迁移的方法,例如Bullfrog,仅能够支持前后兼容的表结构变更。如果表结构变更会引入不兼容的变化,用户需要提前检查表中的数据。因为一旦改变完元数据后,应用就开始使用新版本的表结构,很难再回滚到旧版本的表结构(部分数据已经丢失了)上了。
使用创建视图或者触发器的方法同样可能带来问题,这些方法会锁表,从而导致一段时间内无法对表进行并发更新。
总结:上述表结构变更的问题,主要由于数据库引擎设计时未给与表结构变更足够的重视,导致后续需要通过打补丁的方式来支持用户的表结构变更需求。DDL操作通常被视为危险的,应用开发者通常尽量避免DDL操作。
Tesseract(超立方体):一种在MVCC系统中支持非阻塞、事务性的表结构变更的方法
动机
在使用MVCC的数据库系统中,可以将表结构变更视为一种修改整个表的DML操作,也可以称为“被作为数据修改的数据定义”(data-definition-as-modification, DDaM)(拔高立意)。在这种视角下,只需要对现有的快照行为进行简单的调整,就可以通过MVCC机制几乎零成本地支持表结构变更,并且不需要停机。
具体来讲,事务性的DDL操作可以被视为对表结构的DML语句和对整个用户表的DML语句,这两个DML语句的和。MVCC系统中对表结构版本的支持为DDL的事务属性提供了天生的便利。这样就可以通过修改数据库引擎的方式将DDL语句也作为DML语句处理,而无需依赖视图和触发器等特性。
在实现DDaM时,也有一些挑战要解决。(1)DDL事务需要访问整个表,可能会执行很长时间,从而阻塞其它事务,或者是由于其它事务的不兼容修改而回滚(2)如果严格遵循快照隔离的流程,追踪修改数据的集合,会导致极高的代价。(3)使用旧版本表结构完成的DML事务必须在新版本表结构提交前提交。(如果旧版本表结构完成的DML,在新版本表结构提交后一段时间才提交,那么在新版本表结构提交后开始的读取事务会使用新的表结构来解析旧的数据,从而读取出错误的数据)。为了解决上述问题,需要通过松弛的DDaM调整快照的使用方式,来提供更多的并发,并且减少不必要的回滚。
尽管Terreract聚焦在MVCC系统上,单版本的系统通过直接的修改也可以使用Tesseract的方法。Tesseract还可以和上面的延迟执行数据迁移的方法一起使用,以支持立即提交兼容的表结构变更。
Tessract基于之前的内存数据库ERMIA实现。
基础的DDaM(被作为数据修改的数据定义)
对DDL操作的分类的两个维度
(1)拷贝(COPY): DDL操作是否需要真的需要拷贝或修改数据
(2)验证(VERIFY): DDL操作是否需要验证数据(扫描全部数据并判断是否满足要求)
下表给出了几个典型的例子。
(1)修改列类型(MODIFY COLUMN)时,系统需要扫描整表的数据以确认数据能从旧的类型转变为新的类型,系统还需要修改数据来将数据真的转换为新类型,因此修改列类型同时涉及拷贝和验证
(2)增加非空约束时(ADD CONSTRAINT),系统需要全表扫描以验证数据满足要求