事务
-
什么是事务?事务的特性是什么?
事务是数据库中执行操作的最小执行单元,不可再分,要么全都成功,要么全都失败.
- 事务的使用:开启事务-->执行的操作-->提交事务/回滚事务
-
数据库中执行增删改查操作是否会涉及事务?
-
mysql中执行增删改操作时事务自动开启,执行操作成功后事务自动提交,若失败,自动回滚。
-
mysql事务是自动提交的,我们可以通过设置将事务设置为手动提交。即事务管理变为手动的。
-
手动管理事务的sql:
开启事务: begin;
提交事务: commit;
回滚事务: rollback;
如何关闭事务的自动提交?
set autocommit=off //关闭自动提交,默认值是on 开启的
如何查看自动提交的状态值?
show variables like 'autocommit';
- 手动提交事务案例:
- 事务中 执行的操作 在 未提交之前 对外部不可见
前提:开启2个客户端,在客户端1中关闭事务自动提交
1. 查看当前数据库的autocommit值是否为on
show variables like 'autocommit';
2. 设置当前数据库的事务提交为手动提交
set autocommit=off 关闭自动提交,由我们手动commit:
3. 在客户端1中,手动开启事务,执行insert语句
begin;
insert into ...
select..在本事务中查看表数据,
结果显示可以看到新数据.
4. 在客户端1未提交之前,在客户端2中查询该表中的数据,看数据是否插入成功,结果为未成功.
5. 在客户端1中提交事务,再次在客户端2中查看,可以查看到新插入的数据
以上案例表明:事务中执行的操作在未提交之前对外部不可见.
事务的ACID特性
-
原子性:事务是最小执行单元,不可再分,要么全都成功,要么都失败
-
一致性:从一个一致性状态转换到另一个一致性状态
- 案例1:银行转账,事务前后总额一致
- 案例2:多事务并发执行,同一个事务内多次查询结果要保证一致.除非自己做了修改
- 注意:一致性是基于原子性和隔离性实现的.
- 因为隔离性,事务A 才不会看到事务B的操作
- 也就是说因为隔离性 才有了 一致性
-
隔离性:多事务并发执行,事务之间互不影响
- 前提:
- 同一个事务内,不管别的事务修改数据提交还是没提交,
- 当前事务内只要没有关闭(没有提交commit;),那么查询的数据就是一致的,即使事务D修改了数据,当前事务查询的仍然是之前未修改的
- 即:一个事务内,不同时间点,查到的数据是一致的,除非这个事务自己改了
- 出现的问题就是,如果事务C将数据都删除了,然后事务D查看的还是全部所有的数据,事务D此时在插入一条数据,就会成为脏数据
- 事务C 查询
- 事务D 修改
- 前提:
-
持久性:事务提交后,数据会持久化到数据库中.
Spring的声明式事务
- Spring的声明式事务管理,其实就是对数据库中的事务进行管理
- 这里开启了,就相当于数据库的事务开启了
- 当我们执行的增删改操作,只不过是由spring框架来管理事务了(相当于帮助我们去手动开启事务,成功提交事务,失败回滚)
- begin;
- commit;
- rollback;
面试题:描述Spring的声明式事务:添加注解@Transactional,
业务层某方法中涉及多步增删改操作,需要在方法上方添加注解@Transactional,
即为Spring的声明式事务管理
面试题:Spring中的事务管理有哪些?
声明式事务管理 -- aop的应用:spring
编程式事务管理:自己写事务 向hibernate
-
事务的隔离级别(难点)
-
若两个事务完全并发执行,产生的问题.
- 脏读
- 不可重复读
- 幻读
- 模拟最初事务,没有ACID特性,完全并发
- 如果没有隔离性的话,当并发读取的时候,
- 如下图:事务A 开始,事务B开始
- 事务A读取余额为1000元,事务B花费-500元
- 事务A读取余额为500,事务B,扣款失败,此时还剩1000元
- 事务A 读取余额,花费200,此时余额为300,而事务B还有1000元
- 用户1 的余额应该是800,但是读出了300元,这就是脏数据
- 这就是脏读:某事物读取到了其他事务回滚前的数据
- 脏堵产生的原因:事务的内部对外可见(提交或回滚之前,数据对外可见)
隔离级别:
什么是隔离:?
即:一个事务没执行完,另一个事务已经执行完了
-
读未提交 (read-uncommited)-
-
所有数据库都不会这么干,全乱了
-
-
读已提交(read-commited) --
- 操作一个数据行的:时候
- 优点:解决脏读,缺点:出现不可重复读(其他事务进行了修改)
- 这就是同一个事务的不可重复读,因为事务A,没有做操作,金额却变少了
- 理想是:我没动它,上次看它多少,下一次看它应该还是多少
- 重复读取的时候,可能会发生前后执行查询数据不一致
- (事务A读,事务B改前和B改后,事务A没操作,前后读取的数据却不一致了)
-
可重复读
- (repeatable-read) -- 解决不可重复读,出现问题:幻读(其他事务进行了增删)
- 测试:
- 事务A开启事务,并执行了修改操作,没有提交,没有结束
- 事务B开启事务,并执行了修改操作,此时并没有给修改成功,而是抛出了错误
- 原因是事务A并没有结束,事务B修改的操作在阻塞中
- 事务A执行的时候,给这一行加上了锁,别的事务不能给这行左update操作
- 即:一个事务没执行完,另一个事务已经执行完了
- 5、这就说明 解决了不可重复读的问题,但是会出现幻读
- 锁行
- 可通过给操作的数据行加锁(排它 锁),加锁期间不允许其它事务执行update操作,从而解决不可重复读的问题.
- 当事务A操作的时候,给数据加锁,只要该事务没有结束,释放锁,那么别的事务就不能对其进行update操作
- 但是可以进行增删操作,事务A执行期间,当事务B进行新增commit;的时候,事务A前后读取的总数量就不一致了,这就出现了幻读
- 为什么产生幻读:
- 因为加锁的只是那一行,而增删却没有加锁
- 没有幻读是符合用户的需求的,每次查到的过程都是一致的
- 可通过给操作的数据行加锁(排它 锁),加锁期间不允许其它事务执行update操作,从而解决不可重复读的问题.
-
可串行化
- (Serializable): (顺序执行)-- 解决幻读(通常情况下不会使用,效率太低)
- 就是串联,给表加锁顺序执行,一个执行完结束后,另一个事务才能进来
- 锁表(就是把表锁上了,一个事务操作,别的事务不允许增删改查)
- 实现机制为加表锁
- 四种隔离级别按顺序由低到高
- 隔离级别越高,数据越安全,多事务并发执行效率越低.
- 四种隔离级别下可能会产生的问题:
脏读 不可重复读 幻读
读未提交 √ √ √
读已提交 × √ √
可重复读 × × √
可串行化 × × ×
-
数据库默认的隔离级别:
- mysql 的隔离级别默认为可重复读
- oracle和sql servlet默认的隔离级别为读已提交
- mysql默认隔离级别下测试是否会产生幻读?
- 答案:不会!
- mysql虽然是可重复读,但是在mysql中并不会产生幻读
- mysql中读取数据采用快照读
- 相当于第一次select查询的时候,对结果集拍了一张照,如果后序再次想要执行该select是直接使用之前拍的快照,之前查到的什么,这里显示的就是什么
- 其他事务对其增删,该事务都不会管的
- 也就是说:读取本事务最开始读取到的数据
- 快照读相反的是当前读(每次读取均读取最新的数据)
- mysql中读取数据采用快照读
- mysql虽然是可重复读,但是在mysql中并不会产生幻读
- mysql的存储引擎是(mysql是用来存储数据的,那么就得有存储引擎)
- Innodb -- 支持事务,支持行锁
- 这就说明有些存储引擎不支持事务
MYISAM
Memory
InnoDB
Archive
- 这就说明有些存储引擎不支持事务
- Innodb -- 支持事务,支持行锁
在MySQL的众多存储引擎中,只有InnoDB支持事务,关于事物隔离级别,以下说法错误的是()
A、Read uncommitted、Read committed 、Repeatable read、Serializable四种隔离级别并行性能依次降低,安全性依次提高。
B、脏读是某一事务读取了另外一个事务未提交的数据,不可重复读是读取了其他事务提交的数据,脏读和不可重复读都可以通过事物隔离级别控制。
C、RR隔离级别,只能返回比当前事务早的提交插入、更新、删除值。
D、RR和RC隔离级别都存在幻读,RR隔离级别幻读可以通过next-key lock避免。
C,和删除增加没关系
死锁 - DeadLock
- 什么是死锁?
- 是指多事务并发执行时,出现了事务之间彼此阻塞,无法继续执行的现象。
- 数据库中如何产生死锁?
- 数据库中的锁:
- 按粒度分:
- 表锁,行锁
- 按锁的类别分:
- 共享锁(S锁) 和 排它锁(X锁)
- 共享锁和排它锁满足以下规则:
- 在排他锁上,不可以加其它任何锁,
- 在共享锁上,可以再加共享锁,不可以加排它锁
- 排它锁:一般都是在写(增、删、改)的操作上加的;
- 我再增删改的同时,不允许别人再来增删改,不允许再来增加排它锁
- 排它锁,排斥一切锁
- 共享锁:我锁住的数据可以共享,在读的操作会加
- 事务A读取某一条数据的时候,给它加上共享锁,
- 事务B也读取这条数据,它也需要加一个共享锁,是可以的,因为加的都是共享锁,
- 但是如果加的共享锁,又来一个事务想要执行增删改操作,这是不可以的
- 我正在读,你要改,绝对不可以(我是读之前的还是之后的)
- 按粒度分:
- 数据库中的锁:
- mysql中哪些操作会涉及加锁?
- 1、增删改操作,会默认给 行数据 加 排他锁(x锁): for update
- 2、select操作默认不加任何锁
- 但是,实际中有一些情况需要在select时给行数据加锁,
- 此时是需要手动加锁的
-
查询手动加锁
select ..from... for share; //共享锁 分享
select..from.. for update; //排他锁,泛指所有写(增删改)操作
- 多事务并发对同一条数据执行update(增、删、改)操作,则后来的事务会阻塞,直到前一个事务执行结束.
- 也就是说多个事务并发,结果是看谁最后执行完
步骤:
1. 事务A开启 -->对用户4修改name
2. 事务B开启 -->删除用户4-->出现了阻塞
3. 提交事务A,事务A提交成功,事务B立即执行
4. 最后提交事务B
死锁产生的场景:
- 死锁一定是事务并发产生的
- 事务A操作修改,然后操作删除
- 事务B操作修改,然后操作删除
- 写的操作都会对行上排它锁,发现该行有排它锁,就会进行阻塞等待
- 跟java的死锁原理一样
- java有必须两个对象
- 数据库必须有两张表
- 然后两个线程/事务 分别对两个对象/表操作,没有是释放,然后再交错加错(必须保证不释放),就形成了死锁
- 事务A等事务B结束,事务B等事务A结束,彼此阻塞都无法释放就形成了死锁
- 注意:mysql在发现死锁后的处理:
- 会强制将一端进行回滚:
- 事务并发 后执行的 回滚!
- 事务并发 先执行的 成功!
- 事务执行成功 该事务commit;后,回滚的一方也能看见最新的数据
- 注意:等待超时后,自动回滚!
- 会强制将一端进行回滚:
- 发生死锁后,mysql做了有关死锁的处理
代码实现:
事务A:
1. begin;
2. update user set age=26 where id=1;
5. delete from student where id=101;-->阻塞---发现没有锁了,继续往下执行,执行成功
事务B:
3. begin;
4. update student set age=16 where id=101;
6. delete from user where id=1 -->检测到死锁---mysql做了处理 对事务B强制做了回滚
悲观锁和乐观锁
- 是两种思想,数据库里并不存在这两种锁,是人的两种思想
- 悲观锁:
- 事务总是悲观的认为在我访问期间,总会有其它事务并发访问相同的数据
- 为了确保数据的安全,会在访问时立即给数据加排它锁,保证数据安全,但是效率会降低。
- 注意:悲观锁中若执行查询操作想给数据加锁,此时sql语句写成:
- select ..from ...for update;
- 针对悲观锁效率太低,就出现了乐观锁
- 乐观锁:
- 事务总是很乐观的认为,在本事务访问期间,不会有其它事务并发访问不会给数据加锁,但是多事务并发访问确实存在安全问题
- 为了解决安全问题,乐观锁采用version(版本号机制)来保证数据安全。
- 悲观锁:
- version机制(版本号机制):
- 它是为我们表添加了一个字段,如果使用乐观锁,那么就往表里添加一个字段叫做version,它是个整数类型,初始默认值为0
- 事务在提交时,判断数据库中的version值与事务已获取的version是否相等,
- 若相等,则提交成功;
- 若不相等,说明数据已经被其它事务修改了,事务回滚,之后事务再次尝试执行.
- 事务在提交时,判断数据库中的version值与事务已获取的version是否相等,
- 它是为我们表添加了一个字段,如果使用乐观锁,那么就往表里添加一个字段叫做version,它是个整数类型,初始默认值为0
- 如下:事务A和事务B都访问同一条数据并且执行的是update操作,悲观锁会加排它锁,但是乐观锁不存在排它锁,两个事务都要修改同一条数据,如何保证数据安全?
- 做法:
- java里的操作肯定先获得数据
- 事务A做修改
- 1、事务A先查询用户1的数据(会得到该版本号字段的值)
- 2、执行修改操作,执行结束(事务A执行过程当中,预期结果和执行完的结果是一样的,(采用版本号机制还有一个操作 如下:swap更新的意思)
- 3、修改完后,(又会得到版本号)在提交事务前(立即提交事务时,判断数据库中的version值,是否与步骤1获取到的version值一样,若一致则提交,若不一致则回滚,并自旋)会执行一次CAS(compare and swap)(判断两次获取的版本号是否相等,如相等则提交成功)然后执行提交操作
- 注意:若提交成功,数据里的版本号会自增1,(执行加1),即:提交后数据库里的版本号会变成1;
- 事务B做修改:
- a、事务B查询用户1的数据是0
- ...然后修改完成后,接下来涉及到提交操作
- 提交时执行一次CAS,进行版本号的判断,数据库的版本号是1,a查到的版本号是0
- CAS发现两个版本号不同,则事务回滚,然后尝试重新执行
- 自旋:发现版本号不同->回滚->再尝试重新执行,在指定时间内如果还不成功的话,此时会给出用户提示,重新操作,这就是自旋
- 也就是说,并发情况下谁先抢到时间片,谁成功
- 做法: