之前我们讨论了单一的TX的恢复。一份数据多个备份怎么保证CONSISTENCY,对多个变量一系列操作放在一个TX会如何?
那么有多个并行的TX会如何呢?
你写的东西被别人看见,但是别人用了你的写,你却回滚了。这就会有问题。
如果不对CONCURRENT TX管理的话,系统会出现各种问题, 和DATA RACE 在parallel program 很相似。
一个问题,你能多提取500块
你可以让APPLICATION 来保护,比如去拿锁。 但是这会增加APPLICATION DEVELOPER 的负担,造成重复开发,你的APP 和别人的APP会造成相同的数据块的,这样只拿自己内存里的锁是没法保证的没有DATA RACE的。
如果给STORAGE来做,那么APP就很简单,上面什么都不用太考虑。
今天我们来讲ISOLATION,因为其他三个上一章已经搞定了。
SERIALIZABILITY
2个TX并行执行,执行完的结果,等同于这2个TX串行的执行的结果。
上面2个TX 并行执行 的效果 和串行是等价的,所以他们并行执行没有问题。
下面这种调度的方式就会有问题
下面有2种SOLUTION,一种就是在TX这完全加锁,这个是可以的,但是太慢。
另外一种是用一种细粒度的锁。在OBJECT LEVEL。但是依然不是SERILIZABLE,看下图。
这个是读未提交的问题。
那么我们是不是可以让写锁的时间变长一直到COMMIT,才放锁来解决这个问题。这个时候读锁还是立即释放的锁。这样会出现另外一种不SERIALIZABLE情况。
这个问题是不可重复读,就是在一个TX里面,读一个OBJ读2次结果不一样。
所以对READ来说,SHORT DURATION 也不行,也要变成LONG duration。
这个比第一种GLOBAL 好的,是细粒度的锁的实现。
还可以用读写锁来优化。
二阶段锁定,第一个阶段是GROWING,(集合会慢慢变大);第二个阶段,SHRINKING(发生在COMMIT的时候,只会收缩)
上面这种情况是不可能实现的,因为红色的A 的READ,读锁拿不到。
拿不到锁的时候,可以等,可以ABORT。
如果是等的话,就会出现DEADLOCK。避免DEADLOCK,可以使用按照一定的顺序去拿锁。
如果是看到1个变量拿一把锁,是没法实现的。除非在一开始你就知道要锁哪些变量。
我们可以去检查DEADLOCK,发现DEADLOCK,把其中一个ABORT。
1.通过分析拿锁过程是否有环。
2.我检查TIMEOUT,很长时间拿不到就ABORT自己。(你的TX比较久,就会没法执行完就ABORT自己;轮流拿锁轮流ABORT自己,就会有活锁的情况)
因为TWO PHASE LOCK 锁在OBJECT上,但会有在一个LIST 加2个新的ITEM,这2个OBEJCT 锁是不冲突的。概括的说,在查2次集合的时候,2次的ITEMS 数目不一样。一个会比另一个多。
问题就在于把LOCK放在OBJECT上,在上述情况中,应该把这个锁加在搜索的集合上。
用谓词锁,或者间隙锁(B树上锁一个子树(RANGE))
在实际中,默认不采用SERIALZABLE,因为性能会不好。一个分析的SQL,就会是一个很长的READ ONLY的TX(比如分析1个小时)那么在这个TX,其他的TX会被挡住,所以造成别的功能就挂了。
所以我们需要在一个SERIALZABLE 上的一个优化。突破的方式在一个TRADE OFF的变化。
比如要更好的性能,要牺牲一些CONSISTENCY上的保证。
这就提出了一个MVCC的CONTROL
把所有写操作BUFFER起来(因为不知道最后是COMMIT,还是ABORT,还不希望读到未提交的写),读的时候要选合适的版本。在提交时,系统会验证是不是可以让读VISIBILE。(乐观锁,如果发生了CONFILICT,就产生新的版本)
如果在COMMIT的时候,看写X的时间,如果BUFFER SET里有个新的TX提交的X写,就会ABORT。
下面再看一个例子。
上面的方案 等价的SERIAL 的 顺序如下图
但也是有个反例的。
但是幻读的问题解决了。
如果隐含的条件不在TX里可以表达,那么无论是TWO PHASE LOCK 还是MVCC都不能实现。
上述都是在一台机器上的并发事务。
如果在2台机器上要保持事务,多台机器要达成一致,应该怎么做呢?
1。要所有人都同意
2。如何处理有的人挂了
2 phase commit - > paxos