事物的基本属性(ACID)
- 原子性: 事务涉及的多步操作要么全部执行, 要么就回滚导致全部不执行
- 一致性: 事务执行的中间过程, 对其他事务不可见,
- 隔离性: 事务和事务之间独立执行, 互不干扰
- 持久性: 事务提交后, 事务更新的数据将被写入数据库中, 不可再回滚
很多人区分不了原子性和一致性, 我是这样理解的, 正是因为事务的多步操作只能要么全部执行, 要么全部不执行, 才保证了事物的一致性, 使得数据库中的数据不会停留在事物执行的中间转态, 两者只是描述的方面不一样而已.
事物的隔离级别
事物的隔离级别的设置, 是防止查询操作时带来问题, 不同的隔离级别是能够解决不同层次的问题的
- 读未提交: 可以读取还未提交的事物的结果
- 读已提交: 可以读取已经提交的事物的结果
- 可重复读: 可以重复读取数据, 而不担心数据被删除
- 串行化: 事物不能并发执行, 只能串行
下面我们来看这不同的隔离级别可能在并发的情况下产生哪些问题
数据库事务并发存在的问题
并发的概念百度百科: 并发
- 第一个问题: 脏读
为了解释什么是脏读, 这里我们假设此时此刻数据库中正在执行着A事务和B事务, 而此时的隔离级别是读未提交, 假设A读取的数据是B在事物执行过程中产生的, 而B事物因为执行失败 回滚了,
此时A读取到的数据就是数据库中不存在的 脏数据
- 第二个问题: 不可重复读
同理, 我们假设此时此刻数据库中正在执行着A事务和B事务, 此时事物的隔离级别是读已提交,
事物A读取数据后, 事物B对事物A读取的数据进行了 更新, 从而导致事物A下次读取到的数据和上
一次读取到的不一致, 称为 不可重复读
- 第三个问题: 幻读
假设此时此刻数据库中正在执行着A事务和B事务, 事物A对数据库进行了查询, 得到查询集, 事物B执行时向数据库中插入了数据, 且这些数据满足事物A的查询条件, 也就是说A在下一次查询的时候, 会发现查询集中多了数据, 称为幻读
事物隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | yes | yes | yes |
读已提交 | no | yes | yes |
可重复读 | no | no | yes |
串行 | no | no | no |
解释隔离级别和并发问题的关系
关于并发产生的问题, 我们首先想到的解决方法就是锁, 而在数据库中, 锁就分为了 读锁 和 写锁, 它们之间, 相互排斥, 加了读锁就只能事务进行读, 其他事务就不能对读取到的数据进行更新了.
加了写锁的数据, 其他事务就不能读取到了. 而且加了写锁的数据数据库中并不存在, 只存在于对数据进行更新的事物中.
- 读锁: 事物在进行数据读取的时候, 对读取到的数据加锁, 不允许其他事务更新数据
- 写锁: 事务对某数据进行更改的时候, 对要更改的数据加锁, 不允许其他事务读取数据
- 脏读:
- 事务B更新数据时, 未对更新的数据加 写锁, 使得A能够访问B更新后的数据, 但是B回滚后, 导致该问题
- 不可重复读:
- 事务A对数据读取后, 未对数据加 读锁, 使得事务B能够更新事务A查询集对应的数据库中的数据, 导致该问题.
- 幻读: 事务A读取数据后, 事务B更新了数据库中的数据, 而这个数据刚好符合事务A的查询条件, 这个问题加什么锁都没办法解决, 是并发存在的问题, 只能用事务的串行来解决.
我们再来总结一些问题的避免和锁和事务隔离级别的的关系
隔离级别, 锁, 并发产生的问题之间的关系
- 读未提交: 也就是读取数据时不加锁, 更新数据时也不加锁, 从而引发 脏读 问题
- 读已提交: 读取数据时不加锁, 更新数据时加写锁, 解决了脏读问题, 但是因为读取数据的时候没有加读锁, 因而可能它读取到的数据被另一个事物更改, 导致 不可重复读 问题.
- 可重复读: 读时加读锁, 更新时加写锁, 这样就避免了, 脏读, 和不可重复读的问题. 但是可能会导致 幻读
- . 串行: 幻读 的问题跟锁没有关系, 就算有关系, 那也是数据库级别的锁, 在一个事务对数据库进行读取或更新时, 不允许另一个事务对数据库进行任何操作.
附加:
由于幻读不好理解, 我在这里举个例子:
假设数据库中有这样一张表, 表名是table
id | age |
---|---|
1 | 1 |
现在我使用这样一条查询语句, 代表事务A:
select * from table where age>=1;
可以推测出结果应该是这样的:
id | age |
---|---|
1 | 1 |
单在A事务还未提交时, 另一个事务B执行了这样一条语句, 并提交了:
insert into table values((id=2, age=3), (id=3, age=0));
此时我们的表变成了这样:
id | age |
---|---|
1 | 1 |
2 | 3 |
3 | 0 |
此时相应的根据A的查询条件对应的结果应该是这样:
id | age |
---|---|
1 | 1 |
2 | 3 |
但A 最终 得到的结果, 却是这样:
id | age |
---|---|
1 | 1 |
这就称为幻读, 这是并发带来的问题, 是指同一时刻有多个事务执行, 导致的事务的查询集和事务的查询条件与数据库的映射不相符, 在这个例子里是少了一条数据