MVCC 下的事务管理
Posted 有关SQL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVCC 下的事务管理相关的知识,希望对你有一定的参考价值。
讲讲SQL Server Memory Optimized Table的MVCC.
从单个只访问memory optimized table的语句来讲,snapshot隔离机制本质上是MVCC,因此read uncommitted 和 read committed提供的隔离功能,它都实现了。或者说snapshot的读并不会被其他session的写给阻塞。所以可以理解为它的起点比较高,memory optimized table的访问不支持锁,想要在访问memroy optimized table时候,保证事务前后一致性,并不是不可以。比如一个只访问memory optimized table的事务,开始时候获得一个timestamp,那么在事务中,只要持续访问这个timestamp的snapshot版本即可,或者是由本事务生成的row.那这个事务就是repeatable read或者serializable事务了。
对于既访问memory optimized table和disk-based table的语句,disk-based table是全支持5种隔离级别的,而memory optmized table只支持snapshot, repeatable, serializable.所以他们是一个大事务下的两个小事务,各自维护一份时间序列。
为memory optimized table指定事务隔离级别的时候,有两种可能:
Native Compiled Modules: 在Atomic语句下,指定三者之一;
在T-SQL语句下,使用table hint或者memory_optimized_elevate_to_snapshot。
我们需要讨论在这两种模式下,repeatable read, serializable是怎么作用在memory optimized table上的?我猜测也就是读取事务开始的timestamp,取begin timestamp和end timestamp之间的数据行。任何begin timestamp大于这个事务开始时候获得的timestamp,都不会被读取;任何end timestamp小于这个事务开始时候获得的timestamp,也不会被读取。
serializable的锁模式,锁作用范围,锁作用时间。锁作用范围是数据页,索引页,也就是说在事务范围内,锁定的索引,表值都不允许变,其他事务只能等待,作用时间一直到事务结束。那么对于memory optimized table来说,始终使用MVCC来访问的,就是在事务开始时到事务结束,始终访问一定范围内的snapshot数据行。在访问memory optimized table的事务中,假设一开始访问了符合某一个条件的数据集,过了一段时间后,再访问符合这个条件的数据集,而这个过程中,有其他事务新增了符合这个条件的数据集,那么在本事务最后一次访问符合条件的数据集时,能不能访问到其他事务新增的数据集呢?
这取决于事务的定义:如果是单条语句,那么一条语句就是一个事务,事务是显式自动提交的,两个语句就是两个事务,中间如果有其他事务做了新增,最后一个事务肯定是能访问到新增数据的。因为最后一个语句的timestamp是比新增数据的begin timestamp要新鲜的多。如果是多条语句组成的事务,那么time stamp会在事务开始就指定,此事务就只能访问begin timestamp比事务开始时获取的timestamp小,并且end timestamp比当前获得的time stamp大的数据行。而在这个事务执行过程中,新增的数据行肯定是更大的begin timestamp, 因此不能被此事务读取。
因此repeatable read, serializable事务隔离,正好都满足snapshot的读取逻辑,因此他们是被memory optimized table支持的。
Memory optimized table的更新: 新增,更新,删除操作
删除:就是新增的反操作。唯一不同的是,在提交删除操作的最后阶段,会加上end timestamp, 标记删除。其他事务会读这个end timestamp,判断是否读取。memory optimized table的row都是通过index来串联存储的,所以删除一个row肯定是先从Index搜索,找到对应的索引,再删除链接,标上end timestamp的标记。
更新:是上面两个操作的合并,先执行新增操作,再执行删除。
在所有的操作中,提交数据,并不是只有commit这一步。在commit时候,获取了timestamp,然后做validation,做完validation,写log , 写完log, commit才算正真结束,然后做post process. Post process主要是将transaction id转换成end timestamp或者begin timestamp
repeatable read和serializable的读一致性,是怎么保证的?仅仅靠share lock,保证的是事务当中没有其他写操作可以作用到share lock的数据上,但是其他的只读事务,依旧可以读取这份数据,当事务顺序写入的时候,就会和事务开始读取的数据,不一致。这个时候,这两种事务怎么辨别事务的读一致性已经被破坏了?
在sql server in memory OLTP internals for sql server 2016中举了一个例子:
T1正在更新row<Jane,Helsinki>,将之更新为<Jane, Perth>. 此时,有个并发的事务T3,读取的是<Jane,Helsinki>,用读取的Helsinki去更新另一个人的city。 在T1提交更新之后,T3也提交,此时自动报错为violation repeatable read isolation. 注意,在T1事务上,采用的是serializable read, 而在T3事务上,用的read committed 隔离,而针对<Jane, Helsinki>表T1,采用的是repeatable read. 这里有几个疑问:
T3是repeatable read, 针对访问memory optimized table采用repeatable read 会与访问disk-based table采用的repeatable read不同吗?因为disk-based table的repeatable read是用的share lock一直锁到事务结束,而针对memory optimized table采用repeatable read, 采用的方式,难道还用锁吗?前面提到memory optimized table的访问方式只可能是MVCC级别的访问。而MVCC级别的访问,从一开始就是可以满足事务重复读,一致性读的要求。所以这里虽然指定了repeatable read但是并没有用锁,导致T1的更新才不会阻塞而成功了。
那么T3指定了repeatable read事务,在提交的时候,怎么会被发现是与原先版本冲突了呢?这里也是和MVCC机制有关。因为MVCC是在更新的时候会自动检验,更新的副本,是不是已经被致上最新的end timestamp了。被致上最新的end timestamp不仅仅是被删除了,还有可能是被更新了,更新的步骤就包含了新建和删除。仅仅是读事务,一点问题没有,但如果是写事务,整个事务(所有涉及的表,不管只是被读,还是被写)都必须是事务前后版本一致。这是MVCC自动校验,而不需要人为去实现这么一个校验的操作。
假如将T3的事务设定成serializable,效果应该也一样。
当一个事务既访问了memory optimized table 也同时访问了disk-based table, 这个事务就被叫做cross-container transaction. 我们可以将这种事务看成是一个具有两个子事务的大事务。在每段事务的开始,就给这段事务指定一个timestamp.所以如果事务被设置为snapshot,就会有问题了,这个时候,每个子事务都有不同的timestamp,而最终不知道哪个timestamp会被作为更新的判断标准。
在MVCC模式下,repeatable read和serializable访问表,用的肯定不是锁,特别是share lock来锁定特定的表数据行。两者的实现在MVCC模式下与disk-based table访问使用的原理不一样。repeatable read防止的是不一致读,没有漏读,没有脏读,前后数据一致,而serializable防止的是Phantom,在事务开始之后,提交之前,有别的事务新增了数据,这部分数据是可以被读到的,但是在serializable隔离级别下,就设置为读不到,目的是为了保证数据的serializable.这个时候,用的是共享锁(share lock). 其他更新或者新增的事务会被阻塞。
而在MVCC隔离模式下,repeatable read, serializable的实现就不一样了。只读取事务开始时,begin timestamp比当前timestamp小,并且 end timestamp比当前timestamp大的数据。因此这两者事务通通支持,并且由MVCC在commit阶段,进行validate,决定是否可以提交成功。
以上是关于MVCC 下的事务管理的主要内容,如果未能解决你的问题,请参考以下文章