MySQL事务隔离&实操

文章详细介绍了数据库事务中的隔离级别,包括读未提交、读已提交、可重复读和串行化,以及它们在并发操作中可能导致的脏读、不可重复读和幻读问题。通过示例展示了这些概念,并强调了不同隔离级别在效率和安全性之间的权衡。
摘要由CSDN通过智能技术生成

什么是事务

一次业务交互涉及多行记录的时候(不管是否扩表),需要保证要么都成功,要么都失败。典型的就是转账场景:先查询余额A账户有100,A少100,B多100,如果在查询余额之后,另一个线程在执行从A给C转账,也是查询到A有100,这样继续操作A的账户很可能是负的。

示例准备工作

为了更好演示下面的各种情况, 需要先创建相应的表。

-- 建表语句
CREATE TABLE `t_transaction` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `account` INT NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;
-- 插入数据
INSERT INTO t_transaction (id,account) VALUES(1,1000);
INSERT INTO t_transaction (id,account) VALUES(2,1000);

下面场景需要模拟2个事务,因此需要开2个会话。

事务隔离级别

一般我们说事务要满足ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),这里核心讨论的是隔离性。

首先效率和安全总是矛盾的,所以隔离级别越高说明牺牲的性能越大。在数据库上并发执行事务的时候(效率),就可能出现:脏读、幻读、不可重复读

读未提交(脏读):

对应的隔离级别是读未提交,一个事务还没提交时,他做的变更就被别的事务看到了。

举个例子:

  1. A 的工资是5k,财务在发工资的时候开启一个事务1,给A账户增加了工资5k(此时事务未提交)。

  1. 此时A去查看工资,能看到刚发的工资,很开心。

  1. 结果发工资的事务1 在执行其他记录的时候出现异常,导致事务回滚,A这个时候再去看工资又是没发的情况,这个时候就要骂人了。

示例:

会话1 模拟用户查看的情况

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; # 需要设置隔离级别为读未提交
START TRANSACTION;
SELECT * FROM t_transaction;

会话2 模拟财务发工资

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE t_transaction SET account = account+5000 WHERE id = 1;

注意这个时候还没提交事务。

会话1 查一下记录,发现用户1 的工资已经到账了。

SELECT * FROM t_transaction;

会话2 进行事务回滚

rollback;

此时会话1 再查询的时候会发现到手的工资没了。

从上面可以看出来这个问题还是挺大,读到别人还没提交的数据(脏数据,所以叫脏读),会带来很大的风险。

读已提交(不可重复读&幻读)

对应的隔离级别是读已提交。这2个非常像,而且在mysql 下他们的隔离级别是一致,所以放一起讲。

不可重复读:

事务1 在整个事务中存在多次读取同一数据的情况,第一次查询的时候值是1000。

事务2 在这个过程中修改了这条数据并提交了事务,将值改为2000。

事务1 再一次查询的时候发现值变成了2000 。

在一个事务内2次读到的数据是不一样的,因此叫不可重复读。

幻读:

事务1 需要操作所有工资是1000 的记录,此时查询数据库有100 条记录。

事务2 此时要操作其中一个员工离职,那个员工的工资也是1000 。

事务1 再次读取工资是1000的记录,发现只有99条记录。

在一个事务内2次读到的数据记录数是不一样的,就跟产生幻觉一样。

小结:

从上面的描述可以看到共同点都是同一个事务的2次查询产生的结果不一样,区别是一个记录数是一样的但是内容不一样,另一个则是记录数不一样。

示例:

事务1,第一次查询

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
select * from t_transaction ;

事务2,操作数据,这个时候还没提交。

# 不可重复读
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
update t_transaction set account=account+5000 where id=1;
SELECT * FROM t_transaction;

此时事务1 查询的时候看到的是还是1000。

将事务2的事务提交,再在事务1中查询的时候,发现数据变了。

因此读已提交能解决脏读的问题,但是不能解决不可重复读和幻读的问题。为了解决这2个问题我们需要更严格的事务隔离级别。

可重复读

这是MySQL 的默认隔离级别(repeatable read)。

示例:

事务1 开启查询

set session transaction isolation level repeatable read;
start transaction;
select * from t_transaction

注意这个时候没有commit

会话2 进行插入操作

# 事务1加上共享锁之后,事务2update 操作会被卡住

START TRANSACTION;
update t_transaction set account=account+200 where id=1;
commit;

# 可以查询是否生效
select * from t_transaction

会话1 这个时候继续查询,会发现记录并没有变更。也就是避免了不可重复读的情况。

串行化

隔离级别是Serializable

事务1 进行查询操作,这个时候事务没有commit

set session transaction isolation level serializable;
start transaction;
select * from t_transaction;

事务2 进行update 操作

START TRANSACTION;
update t_transaction set account=account+200 where id=1;

这个时候会发现事务阻塞了

时间太长会超时。

如果在事务2 阻塞的时候,事务1 提交了,那么事务2也能顺利的提交。

不仅仅是update 数据,insert 此时也是不会成功的,说明锁住的是整张表。因此如果是serializable 级别,读写将是一把锁,虽然保证了很强的一致性,但是对性能是巨大的损害,基本不太可能在实际使用。

最后

概念有点多,还是挺容易搞混的,特别是幻读和不可重复读。梳理一下好很多,当然时间久了也可能会忘记。后面会进一步分析事务的实现原理MVCC希望能进一步加深理解,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值