一、事务
概念:作为一个逻辑单元执行一系列的操作,这操作要么全部完成,要么失败回滚。 通俗的理解就是:你去银行转账(这就是一个事务),要么转账成功,要么转账失败。
事务的四个属性:
(1)原子性(Atomicity):
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
(2)一致性(Consistency) :
就是一个事务在执行之前的状态和在执行完成之后的状态都是处于一致的状态。
例如:转账这个事务,A和B在转账之前,他们两个用户的金额总共是1000,这是在事务进行之前的一个状态,
当他们两个进行转账,不管转账的次数是多少,如何转账,在这个事务完成后,他们的总金额还是不会发生改变的。
这就是一致性。
(3)隔离性(Isolation):
在多个用户并发的访问数据库时,比如多个用户同时操作一张表的时候,数据库会为每个用户
都开启一个事务,每个事务相互之间不能有干扰。
数据库的隔离性有多个级别:由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable
(4)持久性(Durability):
就是当一个事务操作完成提交之后,这个改变就是永久性的。
二、数据库读现象
数据库操作存在着隔离性,这是他的一个属性,由于这个属性的存在,一旦隔离性控制得比较弱的时候 就会出现不同读现象, 如:脏读,不可重复读,幻读。
(1)脏读
事务T1修改某个字段的值,然后事务T2读取该值,此后T1撤销了对该字段的更新,或者更新成另外的值才commit到数据库中,
这样T2读取的数据是无效的或者错误的。导致T2依据脏数据所做的操作也是错误的。
简单理解就是:事务A读取了事务B未提交的数据。
业务场景:
老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,
就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
(2)不可重复读
在数据库访问中,一个事务范围内的两次相同的查询却返回了不同的数据。
事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行验证而重新读取,却发现得到了不同的结果。
业务场景:
程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!
程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
(3)幻读
幻读解决了不可重复读的问题,即在同一个事务范围内,两次相同的查询结果是相同的。但是可以新增表中的数据记录。
幻读是指事务T1对表中的数据进行修改,假设修改涉及了表中全部的数据行,同时第二个事务也修改这个表中的数据,
这种修改是向表中插入一条新的数据。后面就会出现操作了T1事务的用户发现表中还有没有修改的数据行,仿佛出现了幻觉一样。
业务场景:
思聪同学中午去食堂吃饭,看到窗边的座位是空的,便屁颠屁颠的跑去打饭,回来后窗边的座位还是空的(解决不可重复读),
便很高兴坐上去准备开始吃饭,这时候却发现如花同学搬了一个小板凳坐在旁边狼吞虎咽(插入一条数据,出现幻读),思聪顿时没有了胃口。
分析:不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
如果需要解决脏读、不可重复读、幻读等这些数据库读现象,就必须相应提高事务的隔离级别。
但是数据库的隔离级别越高,对应的并发能力就越弱,性能也就相应的越差,所以我们还需根据具体的应用场景去权衡。
三、事务的隔离级别
(1)读未提交(Read uncommitted)
事务的最低隔离级别,在这种隔离级别下,一个事务可以读取另外一个事务未提交的数据。
数据库锁实现原理:
事务T在读数据的时候并未对数据进行加锁,事务T在修改数据的时候对数据增加行级共享锁
T1在读取数据时,T2可以对相同数据进行读取、修改。因为T1没有进行任何锁操作;
当T2对记录进行修改时,T1再次读取数据可以读取到T2修改后的数据。
因为T2对数据进行修改只增加了行级共享锁,T1可以再增加共享读锁进行数据读取(尽管T2没有提交事务)
如上所述,这种隔离级别,会导致脏读现象
(2)读提交(Read committed)
在一个事务修改数据过程中,如果事务没有进行提交,其他事务不能读取该数据
数据库锁实现原理:
事务T在读取数据时增加行级共享锁,读取一旦结束,立即释放;事务T在修改数据时增加行级排他锁,直到事务结束才释放。
T1在读取数据的过程中,T2也可以对相同数据进行读取,但是不能进行修改(T1增加的是共享锁,T2也可以增加共享锁,但是不能增加排他锁)。
T1读取结束后,会立即释放共享锁,这时T2可以增加排他锁,对数据进行修改,而此时T1既不能对数据进行读取也不能进行修改,直到T2事务结束。
如上所述,这种隔离级别,解决了脏读问题,但是不能解决不可重复读现象。
(3)重复读(Repeatable read)
事务T在数据读取时,必须增加行级共享锁,直到事务结束;事务T在修改数据过程中,必须增加行级排他锁,直到数据结束。
数据库锁实现原理:
T1在读取数据的过程中,T2也可以对相同数据进行读取,但是不能进行修改(T1增加的是共享锁,T2也可以增加共享锁,但是不能增加排他锁)。
直到T1事务结束后,才会释放共享锁,这时T2才可以增加排他锁,对数据进行修改。
如上所述,这种隔离级别,解决了不可重复读现象,但是这种隔离级别解决不了幻读的问题:
T1进行查询,读取了10条记录,并对十条记录增加了行级锁,此时T2是无法对这10行数据进行修改操作的,
但是由于没有表级锁,它可以增加一条满足T1查询条件的记录。随后T1在进行查询时,会发现虽然10条记录没有改变,但是突然多了一条记录。
(4)序列化读(Serializable)
产生幻读是由于没有进行范围查询时没有增加范围锁。
数据库锁实现原理:
事务T在读取数据时,必须先增加表级共享锁,直到事务结束才释放;事务T在修改数据时,必须先增加表级排他锁,直到事务结束才释放。
T1在读取A表时,增加了表级共享锁,此时T2也可以读取A表,但是不能进行任何数据的修改,直到T1事务结束。随后T2可以增加对A表的表级排他锁,此时T1不能读取A表中的任何数据,更不能进行修改。
如上所述,可序列化解决了脏读、不可重复读、幻读等读现象,但是隔离级别越来越高的同时,在并发性上也就越来越低。