一、哪些引擎支持事务
MySQL是属于插件型的数据库,支持多种存储引擎,包括 MyISAM 、InnoDB、Memory、Infobright 等。
执行器通过调用存储引擎的相关接口,来达到操作数据存储的目的。也就是说,你也可以自己写个存储引擎,只要你能提供相对应的读写接口。
这么多引擎中,MyISAM 和 InnoDB 是最为常用的。
其中,目前只有 InnoDB 支持事务。因此 InnoDB 是目前最为常用的存储引擎。
二、事务的使用方式
2.1 启动方式
2.1.1 being 或者 start transaction
- 在执行到他们之后的第一个操作innoDB 表的语句,事务才真正启动
2.1.2 start transaction with consistent snapshot
- 立马启动事务
正常的业务中,我们一般使用 begin 比较多,但是我们还是要区分开这三种命令的区别。
being 或者 start transaction 实际上并没有区别,在执行这个语句的时候,实际上事务并没正式启动。只有当你接着执行第一个操作 innoDB 数据库表的时候,才是真正启动了。
而 start transaction with consistent snapshot 则不需要进行等待。
2.2 提交方式
2.2.1 主动提交
即我们熟悉的 commit 语句
2.2.2 自动提交
即 autocommit 参数
当 autocommit 为 on 时,InnoDB 会把你的每次执行都当做一个完整的事务,不需要你手动启动或者手动的提交。
当 autocommit为off 时,你每次操作执行,都是不会自动的提交事务。
我们举几个场景例子试试。
CREATE TABLE `test_2022` (
`id` int(11) NOT NULL,
`name` varchar(20) NOT NULL
) ENGINE=InnoDB;
INSERT INTO `test_2022` VALUES (1, '1-name');
INSERT INTO `test_2022` VALUES (2, '2-name');
INSERT INTO `test_2022` VALUES (3, '3-name');
INSERT INTO `test_2022` VALUES (4, '4-name');
注:以下操作均在隔离级别为 “可重复读” 场景下。
假设 autocommit = on 时
mysql> show variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
事务A | 事务B |
---|---|
select name from test_2022 where id=1; | – |
begin; | |
update test_2022 set name=‘mclink’ where id=1; | |
select name from test_2022 where id=1; | – |
此时,你会发现,事务A两次查询到的 name 都为 ‘1-name’
这是因为在 autocommit=on 的情况下,如果主动开启事务,如果没有主动 commit。数据库也不会自动帮你提交。此时,在可重复读的隔离级别上,没有提交的时候做的数据更改是不可见的(除非是update的当前读(for upadate)操作)
如果,你把事务B操作中的 begin;去掉。那么事务A第二次查到的 name就会变成 ‘mclink’
这是因为InnoDB 会把你的每次执行都当做一个完整的事务。
假设 autocommit = off 时。
设置当前会话 autocommit = off
mysql> set session autocommit=off;
事务A | 事务B |
---|---|
select name from test_2022 where id=2; | – |
update test_2022 set name=‘mclink’ where id=2; | |
select name from test_2022 where id=2; | – |
– | commit; |
select name from test_2022 where id=2; | – |
此时你会发现,事务A前两次查询的结果都是 ‘2-name’,第三次才是 ‘mclink’。
这是因为当我们把自动提交给关了,此时当我们执行语句的时候还是会自动开启一个事务,但是并不会自动的提交,除非你手动的去 commit。
2.3 回滚方式
rollback;
回滚事务只有这一条命令,当你在事务中执行了这条命令,事务中的所有操作(更新,插入,删除)都会全部取消。
2.4 查询目前正在执行的事务
select * from information_schema.INNODB_TRX;
三、事务的隔离级别
3.1 四种隔离级别
隔离级别 | 概念 | 严格程度 |
---|---|---|
读未提交 | 一个事务还没提交时,它做的变更就能被别的事务看到 | 很不严格 |
读已提交 | 一个事务提交后,它做的变更才会被其他事务看到 | 有点严格 |
可重复读 | 一个事务执行过程中看到的数据,总是跟这个事务启动时看到的数据是一致的 | 挺严格的 |
串行化 | 对同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务完成后,才能继续执行 | 超级严格 |
innodb 目前支持四种隔离级别。
默认的隔离级别是可重复读。你可以通过执行这些命令看看你数据库的隔离级别是哪种。
-- 查询当前会话隔离级别
select @@tx_isolation;
-- 查看系统当前隔离级别
select @@global.tx_isolation;
我们简单举几个栗子说明一下几种方式的区别。
事务A | 事务B |
---|---|
select name from test_2022 where id=3 | – |
begin; | |
update test_2022 set name=‘mclink’ where id=3; | |
select name from test_2022 where id=3 | – |
– | commit; |
select name from test_2022 where id=3; | – |
当隔离级别为 【读未提交】时。 第一次 name 的值为 ‘3-name’, 后面两次的 name 都是 ‘mclink’。
当隔离级别为 【读已提交】时,第一次和第二次 name 的值都是 ‘3-name’, 第三次才是 ‘mclink’。
当隔离级别为【可重复读】时。我们改一下栗子。
事务A | 事务B |
---|---|
begin; | – |
select name from test_2022 where id=3 | – |
begin; | |
update test_2022 set name=‘mclink’ where id=3; | |
select name from test_2022 where id=3 | – |
– | commit; |
select name from test_2022 where id=3; | – |
commit; | – |
此时,由于【一致性视图】的原因,查到的三次 name 值都是 ‘3-name’;
而第四种串行化,相当于只要有一个事务占用了这条数据,其他的事务都需要等待。相当于一个原子锁。这种方式性能很差,一般都不会使用这种隔离级别。
3.2 脏读、幻读、当前读、快照读
3.2.1 什么是脏读
脏读在业务上是比较严重的事情,当你的隔离级别设置为【读未提交】时,就会出现脏读。
我们也举个例子。
事务A | 事务B |
---|---|
select name from test_2022 where id=4 | – |
begin; | |
update test_2022 set name=‘mclink’ where id=4; | |
select name from test_2022 where id=4 | – |
rollback; | |
select name from test_2022 where id=4 | – |
你会发现,在这种隔离级别下,第一次查询的 name 是 '4-name‘,第二次查询的值是 ‘mclink’。
第三次查询到的 name 值 为 ‘4-name’。
第二次查询到 name 值改变了,这种情况我们就叫做【脏读】。其实就是另一个事务更新了这个数据,但是并没有提交事务,此时却查询到了他的修改,后面他事务回滚了,我们查到的值又变回来了。
用生活中的栗子来讲,就是 老板答应给你加钱(更新数据),但是没有签合同(提交事务)。你就觉得老板给你加钱了(查询到变更的数据)。后面老板又说不加了(回滚事务),你的工资又变回去了(数据又变回去了),像是做梦一样。所以,请不要相信画饼。
3.2.2 什么是快照读
快照读的意思就是,事务启动后,事务内部的查询默认都是以事务启动时的一致性视图为准,这种情况下即使其他事务插入了数据也是不可见。这其实是 MVCC 的机制控制的,每个数据都有自己数据版本,版本对应的值是最新操作事务的ID,通过结合 undo log 就可以达到获取数据的前世今生了。
3.2.3 什么是当前读
我们经常会有增值的操作,比如说把某个数值做加减。
此时你会问,如果是快照读的话,那不是被加的值就不对了。
比如说,老板A说要给你加 20块钱,老板B说要给你加50块钱,你本来是100块的时薪水,然后老板B操作比较快,已经录入了工资系统(提交事务),这个时候老板A如果还是基于100去给你加钱,不就亏了。
因此,实际上,update的操作使用的并不是快照读,而是当前读。相当于更新的时候,取的是最新的数据。
select * from test_2022 where id=4 for update;
这个 for update 就是使用当前读的意思。
3.2.4 什么是幻读
在【可重复读】隔离级别下,普通的查询是【快照读】,是不会看到别的事务插入的数据的。
因此,幻读在“当前读”下才会出现。而且幻读仅专指“新插入的行”。
同样我们也举个例子
事务A | 事务B |
---|---|
begin; | – |
select * from test_2022; | – |
INSERT INTO test_2022 VALUES (5, ‘5-name’); | |
select * from test_2022 for update | – |
select * from test_2022; | – |
commit | – |
事务A的第一次全表查询我们可以查到四条之前插入的记录。当事务A执行插入语句后。
第二次查询我们可以查到五条记录(因为使用的是当前读)
而第三次查询,查到的还是四条记录(快照读)
所以在当前读的情况下,我们是可以看到其他已提交的事务插入的数据的。但是不使用当前读的时候又查不到,这种情况下就叫做幻读。
四、小结
事务是经常使用的保障手段,当某个流程需要满足 “要么全部完成, 要么全部失败” 的场景时,就可以使用事务,本篇文章总结了事务的使用方式,隔离方式,以及 innodb 几种 “读” 的不同概念,其实事务和锁的联系十分紧密,后面我们聊到锁的时候,可以看看如何将他们打通来看。