一、事务定义
- 事务:事务是一个最小的不可在分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元)
- 事务保证多条sql语句要么同时执行成功,要么同时执行失败
- 一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。
- 事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。
二、转账操作理解事务
我们来看一个真实的转账业务,表名为t_act(账户、余额),用户1需要向用户2转账100元
我们来看一个真实的转账业务,表名为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 )
要求事务要么执行成功,要么执行失败,事物内部不能再分
详解:
一旦某条执行失败,数据的写入马上撤回,即,进行回退操作
2. 持久性(I )
事务一旦提交,那么对数据库的更改就是永久的,接下来的任何操作或事故都不能影响已提交的事务
详解:
数据库的数据存储在磁盘中,但为了方便数据的操作,会在内存中放置一些缓存数据;即使缓存中没有数据,操作数据的话,也主要是跟内存打交道。
CPU不直接和磁盘打交道(因为CPU直接操作内存中数据比操作磁盘中数据快),会将操作好的数据放在内存中的缓存(日志)中,再由缓存(日志)定期将信息写入到磁盘中
但内存存在一个问题,无法永久性存储数据。一旦出现事故(如断电),内存中数据就会消失
就可能会出现下述情况:
一但使用者认为它的事务提交了,提交到缓存中,但缓存还没来得及将数据写入磁盘中,电脑就断电了,便意味着我们并未做到数据的持久化(数据持久化:将数据写入到磁盘等不可更改的地方)
解决方案:
一旦缓存当中的数据无法及时写回到磁盘,对于未配置事务的sql语句,我们选择的是放弃对数据的持久化;但如果sql语句配置了事务,那么事务就会把缓存当中的数据持久化写到磁盘中
事务是如何实现上述操作的:
重做日志,即一旦主机重新启动,那么恢复数据,将数据写回到磁盘中(具体如何实现的这里不做深究)
关于上述内容,一位大佬的解释
持久性问题的产生:
背景:Mysql为了保证存储效率,每次读写文件都是先对缓存池(Buffer Pool)操作,
缓冲池再定期刷新到磁盘中(这一过程称为刷脏)。
产生:由于数据不是直接写到磁盘,那么如果主机断电,就会有一部分数据丢失。
解决:通过重做日志(redo log)恢复数据。在每次修改数据之前,
都会将相应的语句写到redo log中,如果主机断电,那么再次启动时可通过redo log回复。
拓展:redo log也需要在事务提交时将日志写入磁盘,它比缓冲池写入快的原因有两点:
redo log是追加文件写,属于顺序IO,缓冲池是属于随机IO,且刷脏是以页为单位,
有一点修改就要整页写入。
3. 隔离性(D )
多个事务同时执行,事务和事务之间不能相互干扰
详解:
如张三给李四转100的同时,王五给张三转账100.都在往张三的账户上写入数据,一旦操作不当,数据就会出错
多线程操作同一块资源会很危险,而隔离就是为了处理操作同一块内存空间的危险
4. 一致性(C )
事物执行的结果必须是使数据库从一个一致状态变到另一个一致状态
详解:
原子性、持久性、隔离性的最终目的就是保证一致性
四、和事务相关的术语
- 开启事务:Start Transaction
- 事务结束:End Transaction
- 事务提交: Commit Transaction ---- 事务回滚: Rollback Transaction
在MySQL中,事务提交与回滚
实现一下
1.创建数据库表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for act
-- ----------------------------
DROP TABLE IF EXISTS `act`;
CREATE TABLE `act` (`id` int(0) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,`money` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of act
-- ----------------------------
INSERT INTO `act` VALUES (1, '张三', '300');
INSERT INTO `act` VALUES (2, '李四', '300');SET FOREIGN_KEY_CHECKS = 1;
2.进行张三给李四转账100操作
(1).提交操作(非事务成功)
update act set money = 400 where id = 2;
update act set money = 200 where id = 1;
(2).提交操作(非事务失败)
update act set money = 400 where id = 2;
update act set money = 200 where id = 1w; #模拟第二条语句失败
(3).提交操作(事务成功)
- start transaction #开始事务
- DML语句
- commit #事务提交
start transaction;
update act set money = 400 where id = 2;
update act set money = 200 where id = 1;
commit;
(4).提交操作(事务失败)
start transaction;
update act set money = 400 where id = 2;
update act set money = 200 where id = 1w; #模拟第二条语句失败
commit;
(5)回滚操作(事务失败)
事务在执行rollback语句回滚到原来的状态
start transaction;
update act set money = 400 where id = 2;
update act set money = 200 where id = 1w; #模拟第二条语句失败
-- 假设这里出现了错误,我们可以回滚事务
rollback;
-- 或者,如果没有错误,我们可以提交事务
commit;
注意:事务提交之后再进行回滚,回滚是不生效的,因为事务提交后,事务的持久性就会起作用,因此,需要先 rollback
再 commit
五、事务的并发问题
1.脏读:
事务A读取到了事务B修改但未提交的数据。
2.不可重复读
在同一个事务内,多次读取同一个数据,但是数据值发生了改变。(可以看成脏读的一种)
3.幻读:
指的是在一个事务中,多次查询同一个范围的数据,却发现有新增或者减少的行。
这是因为在这个事务进行的过程中,另一个事务插入或者删除了符合查询条件的数据,导致前后两次查询结果不一致。(可以看成脏读的一种)
六:事务的隔离等级
为了解决并发所产生的问题,我们提出了事务的隔离级别,隔离级别越高,解决并发产生的问题越多。
背景知识:
- 读锁和写锁,在读数据时上读锁,在写数据时上写锁。
- 数据上读锁后不能被其他事务修改,直到读锁释放。
- 数据上写锁其他事务不能读也不能修改。
隔离性的隔离级别
1.读未提交 read uncommitted
2.读已提交 read committed
3.可重复读 repeatable read
4.串行化 serializable
1.读未提交 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
。
2.读已提交 read committed
事物A和事物B,事物A提交了数据,事物B才能读取到
这种隔离级别高于读未提交 换句话说,对方事物提交之后的数据,我当前事物才能读取到
这种级别可以避免“脏数据” 这种隔离级别会导致“不可重复读取” Oracle默认隔离级别
设置读已提交的隔离级别
set global transaction isolation level read committed;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可以看出,插入但没有提交,是读不出来的,提交之后才能读出来。
不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务已提交的数据。
而不可重复读在一些情况也并不影响数据的正确性,比如需要多次查询的数据也是要以最后一次查询到的数据为主。
3.可重复读 repeatable read
事务A和事务B,事务A提交之后的数据,事务B读取不到,就说事务B是可重复读取数据
这种隔离级别高于读已提交。 换句话说,对方提交之后的数据,我还是读取不到
这种隔离级别可以避免“不可重复读取”,达到可重复读取
- MySQL默认级别
- 解决了 “不可重复读” 和 “幻读”
设置可重复读的隔离级别
set global transaction isolation level repeatable read;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可见,无论是删除还是添加,commit后都是成功的,但是另一边却还是读出原来的数据,这就是可重复读,读取的是备份数据不是真正的数据。
4.串行化 serializable
事务A和事务B,事务A在操作数据库时,事务B只能排队等待
这种隔离级别很少使用,吞吐量太低,用户体验差
这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发
设置串行化的隔离级别
set global transaction isolation level serializable;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;