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)
评论