备注:本文修订于2020年6月18日

1、事务的初步理解

首先要明白事务的概念,事务是指逻辑上的一组操作,这组操作要么同时完成要么同时不完成。

再看一下事务的具体表现形式:默认情况下,数据库的一条sql语句就处在自己单独的事务当中。

最后再深入的分析一下“一条sql语句”。如同i++一样,一条select或者update语句的背后是有多个操作实现的,这些操作要么同事完成要么同时不完成从而组成了事务。

2、多事务的并发问题

执行单个事务不会出现问题,但是多个事务并发执行就会出现问题,犹如多个线程执行i++一样,一个线程的执行结果可能被另外一个线程的执行结果所覆盖。

多个事务并发访问造成的异常分为以下几种情况:

1.脏读。

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。如下面的例子:

1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000

像这样,Mary记取的工资数8000是一个脏数据。

2.不可重复读。

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。如下面的例子:

1.在事务1中,Mary读取了自己的工资为1000,操作并没有完成。
2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务。
3.在事务1中,Mary再次读取自己的工资时,工资变为了2000

3.幻读。

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。如下面的例子:

目前工资为1000的员工有10人。

1.事务1,读取所有工资为1000的员工。
2.这时事务2向employee表插入了一条员工记录,工资也为1000。
3.事务1再次读取所有工资为1000的员工共读取到了11条记录,

3、数据库隔离级别

以上异常情况的发生与数据库隔离级别相关。数据库隔离级别分为几下几种情况:

1.读取未提交(Read Uncommitted)

这是最低的事务隔离级别,读事务不会阻塞读事务和写事务,写事务也不会阻塞读事务,但是会阻塞写事务。这样造成的一个结果就是当一个写事务没有提交的时候,读事务照样可以读取,那么造成了脏读的现象,例如下面的例子:
3393769874.jpg

2.读取已提交 (Read Committed)

采用此种隔离界别的时候,写事务就会阻塞读事务和写事务,但是读事务不会阻塞读事务和写事务,这样因为写事务会阻塞读取事务,那么从而读取事务就不能读到脏数据,如下面的例子:
3229962994.jpg

但是因为读事务不会阻塞其它的事务,这样还是会造成不可重复读的问题。如同下面这个尴尬的例子:
3848173825.jpg

这里我们看到,对于余额而言,在T4时刻买单失败了。因为在T3时刻老婆提交了消费800 元的事务,这时老公可要出洋相了。为了避免这个问题,我们可以使用可重复读的策略,这样就消除了老公无钱买单的尴尬场景。

3.可重复读( Repeatable Read)

采用此种隔离级别,读事务会阻塞写事务 ,但是读事务不会阻塞读事务,但是写事务会阻塞写事务和读事务 。因为读事务阻塞了写事务,这样以来就不会造成不可重复读的问题,但是这样还是不能避免幻影读问题。可重复读是针对于同一条记录而言的,对于不同的记录会发生下面这样的场景:

2382616130.jpg

我们看到老婆在查询之后,老公启动了消费,并先于老婆之前打印账单记录, 所以在T4时刻,打印了1800 元11条记录,这个时候老婆就会去质疑这800 元是不是幻读的。上面和不可重复读很接近,但是我们需要注意的是,不可重复读是针对同一条记录,而幻读是针对删除和插入记录的。为了避免服这个问题我们可以采用序列化的隔离层。序列化就意味着所有的操作都会按顺序执行,不会出现脏读、不可重读和幻读的情况。

备注:事务之间的堵塞是通过锁来实现的,即使我们给事务所涉及的行都加上了行锁,还是无法解决插入新记录造成的幻读问题,因为这些记录原本不存在,自然无法加上行锁。

4.序列化( serializable)

此种隔离级别是最严格的隔离级别,如果设置成这个级别,那么就不会出现以上所有的问题(脏读,不可重复读,幻影读)。但是这样以来会极大的影响到我们系统的性能,因此我们应该避免设置成为这种隔离级别。

注意:在实践中,我们一般采用读取已提交或者更低的事务隔离级别,配合各种并发访问控制策略来达到并发事务控制的目的。

标签: none

添加新评论