一、事务定义
- 事务:事务是一个最小的不可在分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元)
- 一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。
- 事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。
二、转账操作理解事务
我们来看一个真实的转账业务,表名为t_act(账户、余额),用户1需要向用户2转账100元
actno balance
1 500
2 100
进行转账操作
update t_act set balance=400 where actno=1;
update t_act set balance=200 where actno=2;
以上两台DML语句必须同时成功或者同时失败。最小单元不可再分,当第一条DML语句执行成功后,
并不能将底层数据库中的第一个账户的数据修改,只是将操作记录了一下;
这个记录是在内存中完成的;当第二条DML语句执行成功后,和底层数据库文件中的数据完成同步。
若第二条DML语句执行失败,则清空所有的历史操作记录,要完成以上的功能必须借助事务
三、事务的四大特性
1.原子性(A):指一个事物是一个不可分割的工作单位,其中的操作要么都成功,要么都失败.
当事务发现有些语句不能执行时,需要将数据恢复到事务执行前,通过undo log实现。
undo log是mysql中比较重要的事务日志之一,顾名思义,undo log是一种用于撤销回退的日志,
在事务没提交之前,MySQL会先记录更新前的数据到 undo log日志文件里面,
当事务回滚时或者数据库崩溃时,可以利用 undo log来进行回退。
2.持久性(I): 指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
持久性问题的产生:
背景:Mysql为了保证存储效率,每次读写文件都是先对缓存池(Buffer Pool)操作,缓冲池再定期刷新到磁盘中(这一过程称为刷脏)。
产生:由于数据不是直接写到磁盘,那么如果主机断电,就会有一部分数据丢失。
解决:通过重做日志(redo log)恢复数据。在每次修改数据之前,都会将相应的语句写到redo log中,如果主机断电,那么再次启动时可通过redo log回复。
拓展:redo log也需要在事务提交时将日志写入磁盘,它比缓冲池写入快的原因有两点:redo log是追加文件写,属于顺序IO,缓冲池是属于随机IO,且刷脏是以页为单位,有一点修改就要整页写入。
3.隔离性(D): 隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。
下边讲到的事务并发问题就是隔离性的问题,MVCC就是解决这些问题的。
4.一致性©:指事务执行结束后,数据库的完整性约束没有被破坏,开始1000块钱,后面转账结束了,也得是1000块钱。
实现:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。
四、和事务相关的术语
- 开启事务:Start Transaction
- 事务结束:End Transaction
- 事务提交: Commit Transaction
- 事务回滚: Rollback Transaction
五、在MySQL中,事务提交与回滚
对t_act进行提交和回滚操作
(1).提交操作(非事务成功)
update t_act set balance=400 where actno=1;
update t_act set balance=200 where actno=2;
(2).提交操作(非事务失败)
update t_act set balance=400 where actno=1;
update t_act set balance=200 where actno=2;
(3).提交操作(事务成功)
- start transaction #开始事务
- DML语句
- commit #事务提交
start transaction;#手动开启事务
update t_act set balance=400 where actno=1;
update t_act set balance=200 where actno=2;
commit;#commit之后即可改变底层数据库数据
(4).提交操作(事务失败)
start transaction;#手动开启事务
update t_act set balance=400 where actno=1;
update t_act set balance=200 where actno=2w; #这个地方故意让第二个语句出错
commit;#commit之后即可改变底层数据库数据
(5)回滚操作(事务失败)
回滚操作指的是当我们事务提交失败的时候,就需要我们将数据回滚到失败前的时间段
比如,delete一张表,忘加限制条件,整张表没了。
误操作后,能快速回滚数据是非常重要的。
- start transaction
- DML语句
- rollback
start transaction;#手动开启事务
delete from t_act;
rollback;
五、事务的并发问题
**1.脏读:**事务A读取到了事务B修改但未提交的数据。
通俗的讲,当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,
这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,
那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
**2.不可重复读:**事务A查询同一条语句的前后结果不一样。
在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。
那么,在第一个事务的两次读数据之间。由于第二个事务的修改,
那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,
因此称为不可重复读,即原始读取不可重复。
3.幻读:
指当事务不是独立执行时发生的一种现象,例如 第一个事务对一个表中的数据进行了查询,
这种查询涉及到表中的全部数据行。同时,第二个事务向表当中添加了一行数据,
那么,以后就会发生操作第一个事务的用户发现表中还有没有相关的数据没有查询到,
就好像发生了幻觉一样。
六:事务的隔离等级
为了解决并发所产生的问题,我们提出了事务的隔离级别,隔离级别越高,解决并发产生的问题越多。
背景知识: 读锁和写锁,在读数据时上读锁,在写数据时上写锁。
数据上读锁后不能被其他事务修改,直到读锁释放。
数据上写锁其他事务不能读也不能修改。
隔离性的隔离级别
1.读未提交 read uncommitted
2.读已提交 read committed
3.可重复读 repeatable read
4.串行化 serializable
读未提交 read uncommitted
事物A和事物B,事物A未提交的数据,事物B可以读取到。
这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别。
三种并发问题都没解决。
演示:
1.创建表:
create table t_user(id int primary key auto_increment,username varchar(255));
设置读未提交的隔离级别
set global transaction isolation level read uncommitted;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
MySQL8中隔离级别的变量跟之前的版本不一样,之前是tx_isolation,MySQL8改成了transaction_isolation。
读已提交 read committed
事物A和事物B,事物A提交的数据,事物B才能读取到
这种隔离级别高于读未提交
换句话说,对方事物提交之后的数据,我当前事物才能读取到
这种级别可以避免“脏数据”
这种隔离级别会导致“不可重复读取”
Oracle默认隔离级别
设置读已提交的隔离级别
set global transaction isolation level read committed;
查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可以看出,插入但没有提交,是读不出来的,提交之后才能读出来。
可重复读 repeatable read
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
- 事务B是可重复读取数据
- 这种隔离级别高于读已提交
- 换句话说,对方提交之后的数据,我还是读取不到
- 这种隔离级别可以避免“不可重复读取”,达到可重复读取
- 比如1点和2点读到数据是同一个
- MySQL默认级别
- 虽然可以达到可重复读取,但是会导致“幻像读”
设置可重复读的隔离级别
set global transaction isolation level repeatable read;
查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可见,无论是删除还是添加,commit后都是成功的,但是另一边却还是读出原来的数据,这就是可重复读,读取的是备份数据不是真正的数据。
串行化 serializable
事务A和事务B,事务A在操作数据库时,事务B只能排队等待
这种隔离级别很少使用,吞吐量太低,用户体验差
这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,
而不并发
设置串行化的隔离级别
set global transaction isolation level serializable;查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;