一篇文章看懂事务的一致性

原创
2019/03/25 19:40
阅读数 2.7K

一、前言

    事务一直以来是一个玄之又玄的东西,非常难以理解。难以理解倒不是因为事务本身有多难,而是事务这个概念被各种刻意包装,以至于让人晕头转向,摸不着头脑。例如各种抽象的概念,一致性、持久性、原子性、持久性、读未提交、读已提交、可重复读、序列化,Spring也抽象了事务的传播属性,数据库本身又有各种锁,于是我们就晕头了。今天我们就来扒一扒事务,看看事务华丽外衣下的本质。本篇博客结合沈询的分享,加上自己的一些总结。

二、事务要解的最终问题——“一致性”

    一致性是一个名词,其实这里实际上应该当成一个动词来理解,多个参与者达成一致,达成了共识,相互之间不扯皮。这样说可能比较抽象,举个生活中的例子。

     一天,老王和老王老婆,同时去银行取钱,老王查了银行卡账号有1w块钱,于是取了出来,假设老王老婆和老王同时查询,也看到有1w块钱,于是点击取款,最后发现没钱可取了,于是要扯皮了,这就不一致了。

    用一句通俗的话来描述事务的一致性:所有事务的参与方,眼所见,便是心所得。上面的例子中,老王老婆看到了卡上有1w,却没法取,于是就要扯皮了。这就是严苛的一致性所定义的内容。

    下面我们来具体分析一下上面的例子。

    首先引出事务单位的概念,事务单元就是完成一个具体的业务的最小单元,上面的例子中包含两个事务单元。按照正常的时间线,要想不扯皮,应该看到如下执行顺序。

    

        但是扯皮的事情发生了,两个事务单元并行了,于是出现如下的执行顺序。事务单元二左移,与事务单元一并行。

    

    上面的场景似曾相识,其实就是和线程安全所描述的内容一摸一样,《一篇文章看懂Java并发和线程安全》,要想不扯皮,必须让访问的共享资源互斥,用Java代码可以描述成如下的代码:

public class Consistency {

	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		lock.lock();
		try {
			int balance = query();// 查询余额
			if (balance > 0) {
				drawingOutCash();// 取出现金
			}
		} catch (Exception e) {

		} finally {
			lock.unlock();
		}
	}
}

   要想强一致,所有的事务单元串行着执行,这就是事务隔离级别中的SERIALIZABLE,于是就引出了事务的隔离级别。

三、事务的隔离级别

    强一致,必须让所有事物单元串行执行,这便是隔离级别中的SERIALIZABLE(序列化),但是这样系统的性能是可想而知的,几乎不可用,于是需要放宽对锁的要求,所以出现了其他的隔离级别。事务的隔离级别是以性能为由对一致性的破坏,它的出现是为了破坏一致性,而不是维持一致性。

    事务单元与事务单元的关系只有四种:读读、读写、写读、写写

    SERIALIZABLE(序列化)

    

    要想进一步提升性能,于是出现了读写锁,这里就出现了两种隔离级别:REPEATABLE_READ(可重复读)和READ_COMMITED(读已提交)

    REPEATABLE_READ(可重复读):

    读锁不能被升级为写锁,那么对共享资源的写,就进不来,这样“读读”是可并行的,这样会出现幻读,因为在这个级别,表是不会被看做是共享资源的,所以可以insert

    

    READ_COMMITED(读已提交):

    读锁可以被升级为写锁,那么当对共享资源正在读时,可以被写请求升级为写锁,那么这样“读读”、“读写”可以并行,于是出现了幻读、不可重复读等等现象

    

    READ_UNCOMMITTED

    只加写锁,读不用申请锁,这样“读读”、“读写”、“写读”都可以并行,但“写写”还不能并行,于是所有的写都是串行,于是就有了脏读、不可重复读、幻读等等。

    

    这就是事务隔离级别的真相,事务隔离级别越低,并行度越好,一致性越低。

四、一句话总结

    事务的一致性和线程安全所面对的问题一模一样,要想维持一致性,需要保证两点:共享变量的可见性、临界区代码访问的顺序性。

 

快乐源于分享。

   此博客乃作者原创, 转载请注明出处

展开阅读全文
加载中
点击加入讨论🔥(3) 发布并加入讨论🔥
打赏
3 评论
10 收藏
0
分享
返回顶部
顶部