什么是事务

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所做的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消之前到事务开始时的所有操作。

事务的ACID

1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,并发执行不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,其它操作或故障不应该对其执行结果有任何影响,不能回滚。

事务的并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

事务隔离级别

1、Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
2、Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
3、Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
4、Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
图片描述
mysql默认的事务隔离级别为repeatable-read
图片描述

各个隔离级别的情况

CREATE TABLE `account` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(11) DEFAULT '', 
  `balance` decimal(10,2) DEFAULT NULL ,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; 

1、读未提交
(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:
图片描述
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
图片描述
(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:
图片描述
(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:
图片描述
(5)在客户端A执行更新语句update account set balance = balance - 50 where id =4,Lucy的balance没有变成200,居然是250,是不是很奇怪,数据不一致啊,如果你这么想就太天真了,在应用程序中,我们会用300-50=250,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别
图片描述

2、读已提交
(1)打开一个客户端A,并设置当前事务模式为read committed(已提交读),查询表account的所有记录:
图片描述
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
图片描述
(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:
图片描述
(4)客户端B的事务提交
图片描述
(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题
图片描述

3、可重复读
(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录
图片描述
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交
图片描述
(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
图片描述
(4)在客户端A,接着执行update balance = balance - 50 where id = 1,balance没有变成350-50=300,lucy的balance值用的是步骤(2)中的300来算的,所以是250,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。
图片描述
(5)重新打开客户端B,插入一条新数据后提交
图片描述
(6)在客户端A查询表account的所有记录,没有 查出 新增数据,所以没有出现幻读
图片描述

4、串行化
(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:
图片描述
(2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。
图片描述

https://www.cnblogs.com/huano...
https://www.fangzhipeng.com/j...


Jello
181 声望20 粉丝

看得越多,懂的越少,还年轻,多学习