1. 事务的四大特性
事务具有4个基本特征,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Duration),简称ACID
① 原子性
事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一,要么都成功,要么都失败
任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成
② 一致性(Consistency)
事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。
比如:如果从A账户转账到B账户,不可能因为A账户扣了钱,而B账户没有加钱
③ 隔离性
事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的
④ 持久性(Duration)
事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态
在事物进行过程中,未结束之前,DML语句是不会更改底层数据,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据
2. 事务的隔离级别详解
事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。
不同的隔离级别可以解决不同的问题,SQL的标准事务隔离级别包括:
读未提交(read uncommitted): 一个事务还没有提交时,它做的变更就能被别的事务看到。
读提交(read committed): 一个事物提交之后,它做的变更才会被其他事务看到。
可重复读(repeatable read): 一个事物执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。未提交变更对其他事务也是不可见的。
串行化(serializable): 对于同一行记录,写会加“写锁”,读会加“读锁”,当出现锁冲突时,后访问的事务需要等前一个事务执行完成,才能继续执行。
可能导致的问题有
1.脏读: 一个事务读到另一个事务未提交的更新数据。
2.不可重复读: 一个事务两次读同一行数据,可是这两次读到的数据不一样。
3.幻读: 一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
2.1 读未提交
这个相对好理解,读操作不申请锁,与允许读取未提交的修改,也就是允许读脏数据,读操作不会影响写操作请求排他锁
create table T(c int) engine = InnoDB;
insert into T(c) values(1);
- 1
- 2
假设数据表中 T 中只有一列,其中一行的值为 1
对上面的操作如果是读未提交,那么记住A事务是可以读取到B事务修改了但是还没有提交的事务的
v1 的值是2,这时候事务 B 虽然还没有提交,但是结果已经被事务 A 看到了,因此 v2、v3 也都是2
这也就是所谓的脏读问题:一个事务读到另一个事务未提交的更新数据
2.2 读提交
读提交就是说一个事物提交之后,它做的变更才会被其他事务看到,也就是对于事务A,虽然在事务B更改了数据后进行了一次查询,但是这个时候B还没提交事务,那么查询到的还是原来的数据
即v1 是1,v2 的值是 2 ,事务 B 的更新在提交后才被事务 A 看到。所以 v2 的值是 2
这个解决了所谓的脏读问题,但是出现了新问题,那就是不可重复读,也就是说在A事务中不能对V进行重复的读取,不然你就发现出现了问题,在事务B提交前和事务A读取的三次V的值可能不一样
2.3 可重复读
一个事物执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。未提交变更对其他事务也是不可见的
那么对于上面的例子,事务A读取到的值1,值V1值V2不会受到事务B的影响,只有在离开事务后V3才的会被影响
即 v1、v2是 1,v3 是2,之所以 v2 还是1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的
这也就解决了所谓的不可重复读问题,但是出现了幻读
所谓幻读的解释是这样的:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行
我理解来说,上面的意思是幻读实际上是针对多条数据来说的,我们再举一个例子来说
时间 | 事务A | 事务B |
---|---|---|
T1 | 开始事务 | - |
T2 | 查询主键值为1的数据是否存在(假设结果是不存在) | 开始事务 |
T3 | 插入主键值为1的数据 | |
T4 | 查询主键值为1的数据是否存在(由于B位提交的数据对A不可见,所以还是不存在) | |
T5 | 插入主键值为1的数据 | 结束事务 |
T6 | 结束事务 |
由上面我们可以看到因为事务的隔离界别,事务A并不知道事务B插入了主键值为1的数据,所以自己也去插入,这个时候其实已经有了,仿佛凭空产生出现了幻觉,从而发生了错误
2.4 顺序读
顺序读是最严格的事务隔离级别。它要求所有的事务排队顺序执行,即事务只能一个接一个地处理,不能并发
自然也就解决了上面的所以问题