MySQL事务详述
Posted 李子捌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务详述相关的知识,希望对你有一定的参考价值。
- 备战2022春招或暑期实习,本专栏会持续输出mysql系列文章,祝大家每天进步亿点点!文末私信作者,我们一起去大厂。
- 本篇总结的是 《MySQL之事务详述》,后续会每日更新~
- 关于《Redis入门到精通》、《并发编程》、《Java全面入门》、《鸿蒙开发》等知识点可以参考我的往期博客
- 相信自己,越活越坚强,活着就该逢山开路,遇水架桥!生活,你给我压力,我还你奇迹!
目录
一、简介
在MySQL中事务是一个绕不开的话题。事务是一组操作的集合,它是一个不可分割的工作单位,事务可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
在MySQL中并不是所有的存储引擎都支持事务,MyISAM和InnoDB是MySQL最常使用的引擎,MyISAM不支持事务,InnoDB支持事务。
在目前常用的MySQL版本中,事务默认是自动提交的,也就是说在执行完一条DML语句之后,MySQL会隐式的提交事务,执行commit操作。
二、事务操作
在交易系统中,涉及金额的操作往往程序员十分谨慎,而这里是少不了事务的,因此这里用交易为例,演示事务操作。
2.1 错误案例
李子柒向李子捌转账
准备数据
use liziba;
drop table if exists account;
create table account
(
id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) character set utf8 comment '姓名',
money double(10, 2) comment '余额'
) character set utf8 comment '账户表';
insert into account(name, money)
VALUES ('李子捌', 20000),
('李子柒', 20000);
无事务无异常情况下
-- 查询姓名为李子柒的账户余额
select a.money from account a where a.name = '李子柒';
-- 李子柒账户转出10000
update account a set a.money = (a.money - 10000) where a.name = '李子柒';
-- 李子捌账户转入10000
update account a set a.money = (a.money + 10000) where a.name = '李子捌';
此时表中数据保持一致
无事务出现异常情况
select a.money from account a where a.name = '李子柒';
-- 李子柒账户转出10000
update account a set a.money = (a.money - 10000) where a.name = '李子柒';
异常发生...
-- 李子捌账户转入10000
update account a set a.money = (a.money + 10000) where a.name = '李子捌';
数据重新恢复后,在李子捌账户转入金额之前,模拟异常情况,此时表中的数据出现了不一致的情况
2.2 事务使用方式
方式一:
修改事务的自动提交行为,将默认的自动提交设置为手动提交,这样当我们执行DML语句之后,需要手动执行commit事务才会提交。
查询事务提交方式:
select @@autocommit;
默认事务提交方式1,代表自动提交事务
设置事务提交方式为手动提交:
set @@autocommit = 0;
事务提交方式设置为0,代表手动提交
提交事务:
commit;
当一个事务内的SQL语句值执行结束之后,需要执行commit语句手动提交事务,这样数据库才会生效。
回滚事务:
rollback;
案例:
-- 查询事务提交方式
select @@autocommit;
-- 设置手动提交事务
set @@autocommit = 0;
-- 执行SQL
update account a set a.money = (a.money - 10000) where a.name = '李子柒';
update account a set a.money = (a.money + 10000) where a.name = '李子捌';
-- 撤销事务
rollback ;
执行结束后,account表中的金额未发生改变,说明事务生效了
image.png
方式二:
手动开启事务,这样当我们执行DML语句之后,需要手动执行commit事务才会提交。
手动开启事务
start transaction ;
或者
begin ;
提交事务
commit;
回滚事务
rollback;
案例:
-- 开启事务
start transaction ;
-- 执行SQL
update account a set a.money = (a.money - 10000) where a.name = '李子柒';
update account a set a.money = (a.money + 10000) where a.name = '李子捌';
-- 撤销事务
rollback ;
执行结束后,account表中的金额未发生改变,说明事务生效了
上述几个术语根据《MySQL必知必会》中的解释如下:
- 事务(transaction)指的一组SQL语句
- 回退(rollback)指撤销指定SQL语句的过程
- 提交(commit)指将未存储的SQL语句结果写入数据库表
- 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)
最后一点我这里并没有说到。
三、事务特性
事务有四大特性,就是大家熟知的ACID,其分别是:
- 原子性(Atomicity):事务时不可分割的最小操作单元,要么全部成功,要么全部失败
- 一致性(Consistency):事务完成时,必须使所有的数据保持一致
- 隔离性(Isolation):数据库提供隔离机制,保证事务不受外部并发操纵影响的独立环境下运行
- 持久性(Durability):事务一旦提交或者回滚,它对数据库中的数据改变就是永久的
四、并发问题
4.1 事务并发问题简述
MySQL事务在并发情况下会存在三个问题,分别是:
- 脏读:一个事务读取到另一个事务还未提交的数据,称为脏读
- 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称为不可重复读
- 幻读:一个事务按照条件查询数据时,没有对应的数据,但在插入数据时,又发现这条记录存在了,但是再次查询却无法查询出来,称为幻读
MySQL为了解决事务所带来的问题,于是在数据库中引入了事务隔离级别。在MySQL中目前存在如下四种事务隔离级别,事务隔离级别越高,数据安全性越高,但是性能越低,在这四种隔离级别下分别存在的问题如下 (√代表存在,×代表不存在):
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable Read(默认) | × | × | √ |
Serializable | × | × | × |
MySQL默认的事务隔离级别是Repeatable Read(可重复读),查看方式如下:
select @@transaction_isolation;
修改事务隔离级别方式如下:
-- session 表示设置当前会话的事务隔离级别
-- global 表示设置全局事务隔离级别
set [ session | global ] transaction isolation level
read uncommitted | read committed | repeatable read | serializble
4.2 Read uncommitted
创建两个会话(使用两个cmd窗口连接至MySQL),我这里称左边窗口为窗口一,右边窗口为窗口二(后续三种均是这样不再赘述)。
首先将右边窗口的会话事务隔离级别设置为Read uncommitted(读未提交)。
此时在左边会话(左边会话的事务隔离级别为默认Repeatable read)开启一个事务,修改李子捌用户的金额,但不提交事务,然后在右边会话中查询account表的数据。
可以看到此时右边会话中,查询到了左边会话中未提交事务的数据。这就是Read uncommitted事务隔离级别,这种隔离级别下无法解决脏读问题。
4.2 Read committed
恢复account表的数据,并将右边窗口会话的事务隔离级别设置为Read committed(读已提交)。
此时可以看到在Read committed事务隔离级别下,无法查询到未提交事务的数据,因此解决了脏读问题。
但是在这种隔离级别下,它并未解决不可重复的问题(同一个事务中两次读取同一条记录,结果不一样)。
4.3 Repeatable read
恢复account表的数据,并将右边窗口会话的事务隔离级别设置为Repeatable read(可重复读)。在左边会话事务未提交时,查询到的数据均为20000。
然后将左边会话的事务commit,在右边会话再次执行查询语句。可以看到在Repeatable read事务隔离级别中,同一个事务中多次执行同一条查询语句,查询的结果是相同的,不受其他事务的影响,因此这种事务级别解决了不可重复读问题。
但是这种隔离级别下,存在幻读问题,此时只有李子柒和李子捌两个用户,此时在左边会话中开启事务插入一个李子玖用户,并且尝试在右边会话中也插入一个李子玖用户,发现插入失败,但是查询却查不到李子玖这个用户。
此时在右边会话也插入同样的用户,此时右边会抛出主键重复异常,这种感觉就好像出现了幻觉,因此称为幻读。
在Repeatable read 事务隔离级别下,解决了不可重复读问题,但是并未解决幻读问题。
4.4 Serializable
恢复account表的数据,并将右边窗口会话的事务隔离级别设置为Serializable(串行化)。
可以看到串行化事务隔离级别下,如果有一个事务未提交,另一个事务是无法同时对一个表进行操作,即便是查询也不行,这是因为串行化中会在一个事务访问某个表时,给当前表加一个表锁给表中行记录主键加上行锁,只有当事务结束后才会释放,其它事务才能继续对该表操作。可以在MySQL自带的performance schema数据库的data_locks表中查看锁记录。
此时提交左边会话的事务,右边会话才可以继续对该表数据进行操作,可以看到左边插入的数据在右边也可以查询到了,解决了幻读问题。
最后当两边事务都介绍后,该表的相关锁记录也会清除。
以上是关于MySQL事务详述的主要内容,如果未能解决你的问题,请参考以下文章