MySQL的事务详解
- 一、事务的概念
- 二、事务的四大特性
- 三、事务的版本支持
- 四、事务的提交方式
- 五、事务常见操作方式
- 六、并发事务问题
- 七、事务的隔离级别
- 7.1四种隔离级别
- 7.2隔离级别的查看与设置
- 7.3读未提交
- 7.4读提交
- 7.5可重复读
- 7.6串行化
- 八、一致性(Consistency)
一、事务的概念
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败
举例说明
张三给李四转账1000块钱,张三银行账户的钱减少1000,而李四银行账户的钱要增加1000。 这一组操作就必须在一个事务的范围内,要么都成功,要么都失败 ,正常情况: 转账这个操作, 需要分为以下这么三步来完成 , 三步完成之后, 张三减少1000, 而李四增加1000, 转账成功
异常情况: 转账这个操作, 也是分为以下这么三步来完成 , 在执行第三步是报错了, 这样就导致张三减少1000块钱, 而李四的金额没变, 这样就造成了数据的不一致, 就出现问题了
为了解决上述的问题,就需要通过数据的事务来完成,我们只需要在业务逻辑执行之前开启事务,执行完毕后提交事务。如果执行过程中报错,则回滚事务,把数据恢复到事务开始之前的状态
注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务
为什么会出现事务
事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的
备注:我们后面把 MySQL 中的一行信息,称为一行记录
二、事务的四大特性
-
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
-
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作
-
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化(Serializable )
-
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
上述就是事务的四大特性,简称ACID
三、事务的版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持
我们可以通过show engines命令查看对应的存储引擎
show engines;
- Engine: 表示存储引擎的名称
- Support: 表示服务器对存储引擎的支持情况,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用
- Comment: 对存储引擎简单的描述
- Transactions: 表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务
- XA: 表示存储引擎是否支持XA事务
- Savepoints: 表示存储引擎是否支持保存点
四、事务的提交方式
事务提交方式分为两种:
- 手动提交
- 自动提交
查看事务提交方式 :
#方法一:
show variables like 'autocommit';
#方法二:
SELECT @@autocommit ;
通过上图可以发现autocommit
对应的值为on
,表示自动提交事务。如果为OFF
则表示手动提交事务
我们可以通过修改,来让事务变成手动提交模式:
set autocommit = 0;
此时事务就变为了手动提交模式了
如果不想一直手动提交,那么在使用指令将其设为自动提交模式即可:
set autocommit = 1;
自动提交和手动提交的区别:
- 自动提交模式下:如果不是我们主动开启事务,那么我们执行sql语句时,就会自动帮我们提交
- 手动提交模式下:我们不论是否主动开启事务,在我们执行sql语句时,都需要我们自己手动进行提交
五、事务常见操作方式
数据准备
#创建表
create table if not exists account(
id int primary key,
name varchar(50) not null default '',
blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;
事务相关指令:
# 开启事务
begin 或 start transaction
#创建保存点(也可以理解为单机游戏的存档)
savepoint 保存点名
# 回滚到保存点
rollback to 保存点名
#回滚到最开始
rollback
#提交事务
commit
为了方便演示此时,需要先修改隔离级别,这里演示只需要两个会话,所以为特定会话设置隔离级别,可以使用会话级别的命令:
set session transaction isolation level read uncommitted;
演示一:证明事务的开始与回滚
通过上述的准备,我们已经开启了两个会话,此时在右边的会话中查看account表:
然后在左边的会话中,开启事务,向account表中插入数据:
insert into account values (1, '张三', 100);
再使用右边的会话进行数据查找:
此时就可以看到插入了一条数据,使用savepoint指令创建保存点一:
savepoint save1;
再向表中插入一条数据,并进行查看
insert into account values (2, '李四', 10000);
可以发现已经存在两条记录了,这时再将数据回滚到保存点一,并进行查看:
rollback to save1;
可以发现此时就只剩一条记录了。让数据回滚到最开始的状态,并进行查看:
rollback
刚刚插入的所有记录就都没有了。
演示二:证明未commit,客户端崩溃,MySQL自动会回滚。开启事务,会改变提交模式
先查看当前MySQL的提交模式,和account表中的数据:
可以发现这时是自动提交的,然后开启事务,并插入数据:
#开启事务
begin;
#插入数据
insert into account values (1, '张三', 100);
进行数据的查看:
此时让左边的mysql终端崩溃掉,并查看数据:
可以发现:
- 当右边的
终端崩溃
以后,左边查看account时,就没有记录了,从而可以推出事务并没有进行提交,而是进行了事务回滚
- 开启事务后,如果提交模式是自动提交,那么会改变事务的提交模式
演示三:证明commit后,客户端崩溃,MySQL数据不会再受影响,已经持久化
查看表中的数据
开启事务,向表中插入数据,并提交事务
让左边的终端崩溃,然后进行表的查询
可以发现记录依旧存在,证明了commit后,客户端崩溃,MySQL数据不会再受影响,已经持久化
结论:
- 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是否设置set autocommit无关。
- 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
- 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为MySQL 有 MVCC
- 从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
事务操作注意事项:
-
如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
-
如果一个事务被提交了(commit),则不可以回退(rollback)
-
InnoDB 支持事务, MyISAM 不支持事务
六、并发事务问题
-
脏读:一个事务读到另外一个事务还没有提交的数据
-
不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读
-
幻读:一个事务两次或多次读取同一个数据集的时候,读取到的数据数量不一致
七、事务的隔离级别
7.1四种隔离级别
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性。数据库为了允许事务受不同程度的干扰,就有了一种重要特征:隔离级别
数据库的隔离级别分为四种:
-
读未提交【Read Uncommitted】
: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。 -
读提交【Read Committed】
:该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。 -
可重复读【Repeatable Read】
: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。 -
串行化【Serializable】
: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等
7.2隔离级别的查看与设置
查看隔离级别
- 查看全局的隔离级别
select @@global.transaction_isolation;
- 查看当前会话隔离级别
select @@session.transaction_isolation;
设置隔离级别
1.设置全局的隔离级别
set global transaction isolation level 隔离级别的名称
2.设置当前会话的隔离级别
set session transaction isolation level 隔离级别名称
7.3读未提交
将事务隔离级别设置为 读未提交,重启客户端,并开启事务:
set global transaction isolation level read uncommitted;
查询account表,并插入数据
时候右边的终端查看表中的数据
可以发现此时并没有commit提交数据,但是另一个终端在执行事务过程中,可以读取到数据,这就是读未提交,也就是脏读。
7.4读提交
将事务隔离级别设置为 读提交,重启客户端,并开启事务:
set global transaction isolation level read committed;
查询account表,并更新数据
update account set name = '小王' where id = 3;
右边终端进行数据的查看,然后左边提交事务,右边再进行数据的查看
可以发现,左边的终端提交了事务,右边的终端还在事务的执行过程中,但右边却读到了左边终端提交的数据,这就是读提交。此时右边终端的事务受到了左边终端事务的影响,违背了隔离性,产生了不可重复读的问题。
7.5可重复读
将事务隔离级别设置为 可重复读,重启客户端,并开启事务:
set global transaction isolation level repeatable read;
查询account表,并插入数据
insert into account values (3, '小美', 100);
左边会话提交事务,右边对account表进行查询
可以发现,正在执行事务的右边会话并没有读到左边会话提交的插入(修改)数据,这就是可重复读。
MySQL可重复读隔离级别下,是否会产生幻读?
接着上面的操作,在右边会话,查询左边会话插入的数据
此时发现右边会话中,没有这条记录。说明了MySQL中,在可重复读隔离级别下,不会产生幻读。
7.6串行化
将事务隔离级别设置为 串行化,重启客户端,并开启事务:
set global transaction isolation level Serializable
同时对stu表进行查询:
可以发现串行化隔离级别下,读取数据的时候,不会串行化,是共享锁
此时让左边端口对表中的记录进行修改,并观察结果:
此时它就会进行阻塞,当把右边的事务提交以后,观察:
右边提交以后,左边就立马执行成功了。
这也就说明了,串行化隔离级别下,同时读数据是并行化的,当需要插入、修改、删除数据的时候,就是串行化的了
八、一致性(Consistency)
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。而技术上,通过AID保证C