前言
事务的概念并不是一开始就有的,而是伴随着数据库的产生而产生的一种概念,目的是服务于应用层。初衷是为了保证数据安全可靠,现在已成为不可或缺的一部分。
四大特性
伴随着事务的产生,前人总结了四条为了创建一个能使数据安全可靠的事务必须要遵循的特性,如下,
原子性
从字面意思上看,即不可再分的最小单元。一个简单的例子如下,
A账户向B账户转账100元,写成伪SQL语句,
事务开始:
A - 100
B + 100
事务提交.
由此可见,倘若我们将A - 100与B + 100两条SQL语句拆开来分别执行,在并发的情况下肯定会出错,况且如果是涉及真实金额交易的业务,后果不堪设想。
这就是事务的原子性,保证基于应用层业务的SQL语句,在逻辑上不可再分。正如例子所展示的,两个步骤必须一起完成,或一起不完成 (rollback),不能一个已完成了另一个失败 。因此,原子性从一定程度上确保了数据的安全可靠。
隔离性
当多个事务并发访问数据时,隔离性保证每个事务相互独立,不互相影响。并且一个事务处理时的中间状态对其他的事务是不可见的,从而避免出现数据不一致的情况。数据库通过事务的隔离等级来加大锁的力度,也就是说隔离的等级越高,锁的力度越大,与此同时并发的效率显然会下降。所以要用什么等级的锁,具体还要看业务来定。
持久性
已经被提交的事务的修改,应该永远保存在数据库中。
一致性
数据库总是从一个一致性状态转换到另一个一致性状态。
当前状态满足预定的约束(完整性约束)就叫做一致性状态。
例如,A向B转账100元,但A账户只有80元,如果转账成功则A账户余额小于0,但账户余额的完整性约束是必须大于等于0。所以转账失败,事务回滚。
隔离级别
脏读、不可重复读与幻读
脏读
当一个事务访问到了另一个事务未提交的数据,称为脏读。
| 事务1 | 事务2 |
|---|---|
| begin | begin |
| update tb_class set age = 18 where id = 6; | |
| select age from tb_class where id = 6; | |
| commit | commit |
如果此时数据库的隔离级别为RU,那么就可能会发生脏读:
- 如果事务2将
id=6学生年龄更新为18,但未commit,同时事务1读取到了该字段的值,那么此时事务1获得的age为更新前的值 - 如果事务2
commit完成,但又执行了rollback,那么事务1读取到的值为18
幻读
| 事务1 | 事务2 |
|---|---|
| begin | begin |
| select * from users where id = 1; | |
insert into users(id, name) values (1, 'dog'); |
|
insert into users(id, name) values (1, 'dog'); |
commit |
| commit |
如表格所示,事务1第一次读是确认有无数据,然后在执行插入数据时报错,因为此时事务2已经事先插入完成了数据并且commit,像这种情况的称为幻读。
不可重复读
一个事务读取同一条数据两次,得到的数据不一致,称为不可重复读。
| 事务1 | 事务2 |
|---|---|
| begin | begin |
| select age from tb_class where id = 6; | |
| update tb_class set age = 18 where id = 6; | |
| select age from tb_class where id = 6; | commit |
| commit |