事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
可以简单的理解为:对数据的一次操作就是一个事务。
事务的特性
事务具备四个特性:即原子性(Atomicity)、一致性(consistency)、隔离性(Isolation)、持久性(Durability),简称ACID。
- 原子性: 事务作为一个整体被执行,其中对数据库的操作要么全部执行,要不都不执行(有始有终)。
- 一致性: 事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束(表里如一)。
- 隔离性: 多个事务并发执行时,一个事务的执行不应该影响其他事务的执行(不多管闲事)。
- 持久性: 被提交的事务对数据库的修改应该永久保存在数据库中(一诺千金)。
脏读、不可重复读、幻读
不同隔离级别与读问题的对照关系
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read Uncommitted) | 可能 | 可能 | 可能 |
读已提交(Read Committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
读未提交(Read Uncommitted): 所有读问题都可能发生,一般不会使用这种隔离级别。
读已提交(Read Committed): 只能避免脏读发生,Oracle的默认隔离级别。
可重复读(Repeatable Read): 能够避免脏读和不可重复读发生,MySQL中InnoDB引擎默认的隔离级别。
可串行化(Serializable): 可以解决所有读问题,但由于是串行执行,性能相当一般,所以通常也不会使用。
在MySQL中,可重复读级别就解决了幻读的问题。
假设有一张表:
1 | CREATE TABLE `K_V` |
表中有如下数据:
1 | +------+------+ |
设置数据为手动提交事务:
1 | set autocommit=0; |
脏读(Dirty Read)
脏读是指一个事务(A)读到了另一个事务(B)未提交的数据。
比如,事务A读到事务B并未提交的数据,恰好事务B因某些因素而导致了事务回滚,那么刚刚事务A就相当于读到了实际上并不存在的数据,这就是脏读。
1 | set session transaction isolation level read uncommitted; |
事务A | 事务B |
---|---|
begin; | |
select value from k_v where id = 1;(value=100) | |
begin; | |
update k_v set value = value + 100 where id=1;(value = 200) | |
select value from k_v where id = 1;(value = 200) | |
rollback;(value = 100) |
不可重复读(Unrepeatable Read)
不可重复读是指在一个事务内对同一条记录(可以理解为根据同一个id查询)进行多次查询的结果却不一致。
比如,事务A查询了一次账户余额。之后,事务B在该账户中扣除了一笔钱(比如自动还款)并提交了事务。这时事务A再次查询账户余额,就会发现余额变了,这就属于不可重复读。
1 | set session transaction isolation level read committed; |
事务A | 事务B |
---|---|
begin; | |
begin; | |
update k_v set value = value + 100 where id = 1;(value = 200) | |
select value from k_v where id = 1;(value = 100) | |
commit;(value = 100) | |
select value from k_v where id = 1;(value = 200) |
幻读(Phantom Read)
幻读是指在同一个事务内进行多次操作之间,产生了新的数据或删除了已有数据,并对后续的操作造成了影响。
比如,事务A统计了id大于1的数据。之后,事务B插入了一条id为4的数据并提交了事务。这时事务A再次统计id大于1的数据,就会发现多了一条,这就产生了幻读。
事务A | 事务B |
---|---|
begin; | |
select count(*) form k_v where id>1;(两条) | |
begin; | |
insert into k_v values(4,100); | |
select count(*) from k_v where id>1;(两条) | |
commit; | |
select count(*) from k_v where id>1;(三条) |
区别
脏读指的是一个事务读到了其他事务未提交的数据。
不可重复读指的是一个事务中多次读到同一条(多条)数据发生了变化,重点在于表里已经存在的数据被其他事务修改了(update)。
幻读指的是一个事务被其他事务插入或删除的数据所影响,重点在于事务开始后,其他事务插入或删除了数据(insert/delete)。
脏读、不可重复读、幻读是在并发事务的情况下才发生的。为了解决这些问题,数据库引入了隔离级别,并且不同的隔离级别可以解决不同的问题。