MySQL- 技术专题 - 事务和并发一致性问题

用户头像
李博@Alex
关注
发布于: 2020 年 10 月 13 日
MySQL-技术专题-事务和并发一致性问题

什么是事务

事务指的是满足 ACID 特性的一组操作,通过 Commit 提交一个事务,也使用 Rollback 进行回滚。

事务是并发控制的单位,一系列操作组成的工作单元,该工作单元内的操作是不可分割的,也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。 

事务的结束有两种,当事务中的所有步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作。 

事务的ACID

1. 原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。

回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

2. 一致性(Consistency)

数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。

3. 隔离性(Isolation)

一个事务所做的修改在最终提交以前,对其它事务是不可见的。

4. 持久性(Durability)

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。使用重做日志来保证持久性。

事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:



1、只有满足一致性,事务的执行结果才是正确的。

2、在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就能满足一致性。

3、在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。

4、事务满足持久化是为了能应对数据库崩溃的情况。

并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。

T1是指事务1,T2是指事务2

丢失修改:两个事务同时操作相同数据,后提交的事务会覆盖先提交的事务处理结果,通过乐观锁就可以解决,悲观锁也可以。

例如:T1和T2两个事务都对一个数据进行修改,T 1 先修改,T 2 随后修改,T 2 的修改覆盖了 T 1 的修改。

读脏数据:事务A读取到了事务B已经修改但尚未提交的数据,如果事务B回滚,A读取的数据无效,不符合一致性

例如:T1修改一个数据,T 2 随后读取这个数据。如果 T 1 撤销了这次修改,那么 T 2 读取的数据是脏数据。

不可重复读:事务A读取到了事务B已经提交的修改数据,不符合隔离性,也不符合一致性

例如:T 2 读取一个数据,T 1 对该数据做了修改。如果 T 2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

幻读:事务A读取到了事务B提交的新增数据,不符合隔离性,也不符合一致性

例如:T 1 读取某个范围的数据,T 2 在这个范围内插入新的数据,T 1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。

产生并发性一致性问题,那肯定是不符合一致性,只有满足了一致性,事务的执行结果才是正确的。

产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。

数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

MySQL的共享锁和排它锁

1、共享锁

共享锁也叫读锁(Shared Lock),简称S锁,原理:一个事务获取了一个数据行的共享锁,其他事务能获得该行对应的共享锁,但不能获得排他锁,即一个事务在读取一个数据行的时候,其他事务也可以读,但不能对该数据行进行增删改。

2、排他锁

排他锁也叫写锁(Exclusive Lock),简称x锁,原理:一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁(排他锁或者共享锁),即一个事务在读取一个数据行的时候,其他事务不能对该数据行进行增删改查。

隔离级别

1、读未提交(READ_UNCOMMITED)

(1)事务对当前被读取的数据不加锁。

(2)事务在更新某数据的瞬间(发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。

理解:

1、事务B更改了某个数据,此时加的是行级共享锁,事务A还是可以读取到这个更改了的数据,如果事务B回滚,事务A读取到的数据发生变化,造成脏读。

2、事务A读取某个数据,事务B对其修改并提交,事务A再次读取,得到的是不一样的结果,造成不可重复读

3、同理会造成幻读,因为增加了新的一行数据,那行数据没有添加排它锁

2、读已提交(READ_COMMITED)

(1)事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完,立即释放行级共享锁。

(2)事务更新某数据的瞬间(就是发生更新的瞬间),必须对其加行级排他锁,直到事务结束才释放。

理解:由(2)可以知道,事务B修改某个数据的时候,对当前行添加了行级排它锁,于是事务A直到事务结束才能访问这个数据,如果这个数据发生了回滚,也不会造成脏读,但如果数据没有回滚,两次查到的数据不一致,造成不可重复读,也会造成幻读,因为只是对修改的行加了排它锁,其他行没有添加。

3、可重复读(REPEATABLE_READ)

(1)事务读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,事务结束才释放。

(2)事务更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,事务结束才释放。

理解:事务A读取某个数据的时候,添加了行级共享锁,只能读不能改,因此不会造成修改,也就不存在脏读和不可重复读,但是幻读还是可能存在的,因为只是当前行添加了排它锁,其他行没有锁。

4、可串行化(SERIALIZABLE)

(1)事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放。

(2)事务在更新数据时,必须先对其加表级排他锁,直到事务结束才释放。

理解:整个表都添加了锁,也就不存在增删改了,但是这样会大大降低数据库的性能,所以不建议使用这个隔离级别。

不可重复读和幻读的区别

不可重复读重点在于 update 和 delete ,而幻读的重点在于 insert。如果使用锁机制来实现这两种隔离级别,在可重复读中,该 sql 第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。

但这种方法却无法锁住 insert 的数据,所以当事务 A 先前读取了数据,或者修改了全部数据,事务 B 还是可以insert数据提交,这时事务 A 就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。

需要 Serializable 隔离级别,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。

MySQL默认的隔离级别是:可重复读(REPEATABLE_READ)



用户头像

李博@Alex

关注

我们始于迷惘,终于更高的迷惘. 2020.03.25 加入

一个酷爱计算机技术、健身运动、悬疑推理的极客狂人,大力推荐安利Java官方文档:https://docs.oracle.com/javase/specs/index.html

评论

发布
暂无评论
MySQL-技术专题-事务和并发一致性问题