事务的概念
事务是访问数据库的一个操作序列,数据库应用系统是通过事务集来完成对数据的存取,事务的正确执行使得数据库从一种状态转换成另一种状态(可简单理解为对数据的一次操作过程就是一次事务),一个事务中可能需要执行多条SQL语句。
事务的回滚和提交
ROLLBACK:事务的回滚
COMMIT:事务的提交,提交之后的事务不能再进行回滚
一组业务整体处理的行为叫一个事务。这一组的业务都能成功处理,就可以把这个事务提交来保存结果。但如果一组业务任何一个地方出现差错的话,就认为这事务不成功,需要回滚来撤消之前的操作。
例如:去银行转账,的时候,有可能你的账户中的钱扣了,但是目标账户的钱没有增加,这个时候银行就会用事务回滚,不保存刚才的操作。
多用户下的死锁
在多个用户同时操作一条数据的时候,如果前面一个用户个更新了数据但是没有提交事务(更新之后没有执行 COMMIT 命令提交事务),则后面的用户都能操作当前的数据,之后等待上一个用户提交或是滚事务之后才能进行操作。
当多个用户操作同一条数据的时候,数据库会为数据上锁,只有当上一个事务提交或者回滚事务之后下一个用户才能操作,否则下一个用户将处于线程阻塞状态。
注意:当前一个用户在查询数据的时候,后一个用户去更新数据,默认情况下后一个用户是不会阻塞的,但是如果查询语句后面加上了“FOR UPDATE”后一个用户就会阻塞。
悲观锁和乐观锁
悲观锁与乐观锁的区别在于悲观锁认为数据跟新时一等会发生冲突,因此需要对数据进行加锁,以保证数据的一致性,
乐观锁与悲观锁不同,它认为数据不存在冲突,因此,需要在提交时保证数据的一致性,如果不一致,则返回错误,由程序本身的逻辑进行处理
事务的特性
1.原子性(Atomicity)
一次事务中如果进行多个数据的操作业务,最终的结果要么是所有操作都成功,要么所有的操作都失败
2.一致性(Consistency)
一致性就是数据表中的数据更新要求合乎逻辑的特性,满足了原子性不一定满足一致性
例如:A转给B 100 元,最终的结果为A减少了100元,但是B只增加了十元。这个事务时成功的满足原子性,但是不合乎逻辑不满足一致性。
3.隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如同时操作一条数据,数据库为每个用户开启事务(为该数据上锁,其他事务在上一个事务操作完毕之前是不能操作数据的),不能被其他事务所干扰,多个并发事务之间要相互隔离。
4.持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交的事务操作。
如果事务没有隔离性会发生的几种情况
1.更新丢失
如果多个用户操作数据,基于同一个查询结构对表中的数据进行修改,那么后修改的记录就会覆盖前面修改的记录,前面的修改就丢失了,这就叫做更新丢失。
第一类更新丢失:事务A撤销时覆盖了事务B已经提交了的数据。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入1000元修改余额为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元修改余额为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复成1000元(更新丢失) |
2.第二类更新丢失:事务A覆盖了事务B已经提交的数据,造成事务B的操作丢失。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元修改余额为900元 | |
T6 | 提交事务 | |
T7 | 汇入1000元修改余额为1100元 | |
T8 | 提交事务 | |
T9 | 账户余额为1100元(丢失更新) |
解决办法:对行加锁,只允许一个更新事务
2.脏读
脏读是指在一个事务处理过程中读取了另一个事务未提交的数据
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元修改余额为500元 | |
T5 | 查询账户余额我500元(脏读) | |
T6 | 撤销事务 余额恢复为1000元 | |
T7 | 汇入100元把余额修改为600元 | |
T8 | 提交事务 |
解决办法:如果在第一个事务提交前,任何其他事务不可以读取其修改过的值,则可以避免该问题。
3.不可重复读
一个事务对同一行数据重复读取两次,但是却得到了不同的结果,事务A读取某一数据后,事务B对其做了修改,当事务A再次读取数据时得到与前一次不同的值。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出1000元修改余额为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(不可重复读) |
解决方法:如果只有在修改事务提交之后才可读数据,则可以避免该问题
不可重复读和脏读的区别是:脏读是读取了某一个事务未提交的数据,而不可重复第一则是读取了前一个事务更新了的数据。
4.虚读(幻读)
同一个事务中两次统计一同张表得到的结果不一致。
时间 | 查询事务A | 添加事务B | |
---|---|---|---|
T1 | 开始事务 | ||
T2 | 开始事务 | ||
T3 | 统计数据结果为10 | ||
T4 | 新增一条数据 | ||
T5 | 提交事务 | ||
T6 | 统计数据结果为11(虚读) |
解决办法:在查询事务完成之前,任何其他事务到不能更新数据,则可以避免该问题。
幻读和不可重读读:都是同一个事务多次读取了其他事务提交的数据导致每次读取的结果都不一致,所不同的是不可重复读,读取的是同一条数据,而幻读针对的是一批数据整体的统计。
事务的隔离级别
我们以MySQL数据库来分析数据库的四种隔离级别
1. Read uncommitted(读未提交)
如果一个事务开始写数据,则另一个事务不允许同时进行写操作,但是允许其他事务读操作。
只能解决更新丢失,还可出现幻读,脏读,不可重复读
2.Read committed(读提交)
如果一个事务正在进行写操作那么不允许任何事务进行读写操作,如果是读操作,则允许其他事务进行读写操作。
解决了更新丢失,脏读,可是却存在不可重复读。
3.Repeatable read(可重复读取)
如果一个事务内需要多次读取同一数据,那么在这个事务还没有结束之前,其他事务不能访问该数据。
解决了更新丢失,脏读,不可重复读但是会出现幻读。
4.Serializable(可串行化)
它要求事务只能一个一个的执行,不能并发执行,序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少用,在该级别下事务顺序执行。
解决了更新丢失,脏读,不可重复读,幻读。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read uncommitted) | √ | √ | √ |
读提交(Read committed) | × | √ | √ |
可重复读(Repeatable read) | × | × | √ |
可串行化(Serializable) | × | × | × |
以上四种隔离级别,最高的 Serializable 级别,最低的是 Read uncommitted 级别,级别越高执行效率就越低,像 Serializable 级别是以锁表的方式使得其他线程只能在锁外等待。