MVCC 的局限性
Opened this issue · 5 comments
MVCC (Multi-Version Concurrency Control) 多版本并发控制 在一些只支持单行事务的系统中比较流行也很实用,比如 HBase,但是却依然有两个很大的局限性。
实现 MVCC 最核心的一点就是在事务提交时检测冲突,如果两个事务发生了冲突,可以对其中一个事务进行 rollback 然后抛出异常,或者在 rollback 后在数据库内部重新执行事务。
MVCC 的局限性正好就是在事务发生了冲突之后的处理动作,如果对事务 rollback 然后抛出异常,那么对于客户端是很不友好的,在并发事务数稍微多一点时,发生冲突的概率很大,客户端收到事务冲突异常是很常见的,这种异常只跟具体数据库的内部实现逻辑有关,并不是一种业务相关的异常,如果在客户端代码中随处都要考虑处理这种异常会让开发人员崩溃。
如果在 rollback 后不抛异常,而是在数据库内部重新执行事务会如何呢?
由于在数据库内部看到的事务跟在客户端代码中直观看到的事务不是严格相等的,如果一个事务只涉及单行记录,那么在执行事务重做时并不会产生什么问题,如果一个事务涉及多行记录,或者在客户端代码的同一个事务中后执行的语句需要依赖前一条语句的执行结果,那么在数据库内部重新执行事务就有可能造成奇怪的行为。
比如,这里就有个例子: http://blog.sina.com.cn/s/blog_4673e6030102xlqx.html 这里是官方解释: https://www.zhihu.com/people/huang-dong-xu/posts
例子中的场景就是在同一个事务中后执行的语句需要依赖前一条语句的执行结果,前一条语句返回的结果说明账号里还有钱,所以就执行后一条语句,但是在事务提交时因为数据库使用了 MVCC ,导致提交时产生了冲突,然后事务被 rollback,紧接着在数据库内部重新执行事务,但是因为在内部执行事务时数据库只看到这个事务中的两条语句,无法知道在客户端代码中的逻辑是后一条语句的执行条件是受第一条语句的结果影响的,所以数据库内部只能顺序执行两条更新语句,最后导致结果不一致。
这就是使用 MVCC 后不是所有的事务在 rollback 后都能重新执行的原因,因为数据库内部看到的事务并不严格等价于客户端中的事务。
我很早就意识到 MVCC 的局限性,虽然在 Lealone 数据库的实现中也继续沿用了 MVCC 这个词,但并不是严格意义上的 MVCC,更精确一点说应该是: 行/列锁 + Copy On Write,也就是更新记录时,把原有记录 copy 一份(浅copy),然后再对记录加锁,这样不会阻塞读操作,只阻塞写操作。这样就避免了传统 MVCC 的两个局限性,另外,因为线程不与事务绑定,所以事务在对行/列加锁后,即使在当前线程中执行的事务被阻塞了,也不会阻塞当前线程,当前线程会转去执行其他事务。
例子中加了where条件,这样没有依赖其他语句吧,我的理解是读取的快照版本不是最新的。
把那例子中在客户端交互式执行sql的方式转成代码流程,每执行一条sql,然后看返回结果,再依据返回结果判断是否要执行下一条,这就是有依赖关系了。他在客户端只是肉眼看结果判断。
肯定是冲突了的,你去看看官方的文档修改历史,然后对比那个案例发表在网上的时间。
当然,那文章肯定不是我写的,只是我很早就知道MVCC的问题所在。
意思是 Lealone 数据库 在遇到并发写同一列时,不采取重试,而是阻赛挂起,但不影响线程
对啊,这就是数据库异步化后线程与事务分离的优势。
而且我这里说的锁并不是java/jvm层面的锁,仅仅是个占位标志一样的东西,标明当前的记录或字段被某个事务修改中。