MySQL 的事务隔离级别

我们先来回忆下什么是事务。

考虑一种场景。银行转账,甲转 100 给乙,分为两步:

  • 甲扣除 100
  • 乙增加 100

如果甲的钱扣了,乙的钱没有增加,那么数据就会不一致,这时候就要用到事务。因为事务的一个特性,就是原子性:要么不做,要么全做

一、事务的基本要素(ACID)

  1. 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  3. 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二、事务的隔离级别

我们回到转账的例子。

甲扣除 100 后,如果事务 A 没有提交,那么另外一个事务 B 来查询甲的余额,那应该是多少呢?这时候就和事务隔离级别有关了。

事务隔离级别有以下 4 种:

  1. 读未提交(read-uncommitted)
  2. 不可重复读(read-committed)
  3. 可重复读(repeatable-read)
  4. 串行化(serializable)

Mysql 的默认隔离级别为可重复读(repeatable-read)。

2.1 读未提交(read-uncommitted)

我们来看转账的例子,假设 A 一开始的余额为 500。表名为 account,表数据为:

idnameamount
1500
20

设置隔离级别为读未提交。

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400)
select amount from account where id = 1; (amount = 400)
roll back;
commit;

可以看到,事务 B 读取到了事务 A 未提交的数据。实际上事务 A 进行了回滚,amount 仍未 500,但是事务 B 读到的为 400 ,出现了脏读。因此,实际中,一般不会采用这种隔离级别。

2.2 不可重复读(read-committed)

设置隔离级别为不可重复读。

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400)
select amount from account where id = 1; (amount = 500)
commit;
select amount from account where id = 1; (amount = 400)
commit;

可以看到,在事务 A 提交前的改动,事务 B 是读取不到的。只有 A 事务提交后,B 才能读取到事务 A 的改动。

我们看到,在事务 B 中,先后两次读取,amount 的值是不一样的,这就是不可重复读

不可重复读会有一个问题,假如事务 B 的逻辑是分两次读取 amount,分别插入到两个表中,那么就会出现数据不一致。

2.3 可重复读(repeatable-read)

设置隔离级别为可重复读。

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400)
select amount from account where id = 1; (amount = 500)
commit;
select amount from account where id = 1; (amount = 500)
commit;

可以看到,不论事务 A 是否提交,事务 B 读到的 amount 值都是不变的。这就是可重复读

除了上面提到的脏读、不可重复读,还有一种情况是幻读:在事务中,前后两次查询,记录数量是不一样的

由于 mysql 的 RR(可重复读)一并解决了幻读的问题,所以我们直接看上述场景,在 mysql 中的表现:

事务A事务B
start transaction;start transaction;
select count(*) from account; (count = 2)
insert into account values(3,‘丙’,0);
commit;
select count(*) from account; (count = 2)
commit;

可见,在事务 A 提交前后,事务 B 查询的结果数量是一直的,并没有出现幻读的情况。

2.4 串行化(serializable)
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

事务隔离级别为串行化时,读写数据都会锁住整张表。因此事务 A 执行的时候,事务 B 会阻塞,不存在并发问题。

三、事务的并发问题

刚刚已经谈到了常见的问题,我们再来总结下:

  1. 脏读:事务 B 读取了事务 A 更新的数据,然后 A 回滚操作,那么 B 读取到的数据是脏数据。
  2. 不可重复读:事务 B 多次读取同一数据,事务 A 在事务 B 多次读取的过程中,对数据作了更新并提交,导致事务 B 多次读取同一数据时,结果不一致。
  3. 幻读:事务 B 多次统计数据量,事务 A 在事务 B 多次统计的过程中,对数据作了新增或删除操作,导致事务 B 多次统计数据时,结果不一致。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

四、快照读、当前读

我们先考虑下这样一个问题(基于 MySQL 的默认隔离级别可重复读 RR )。

假设甲同时转账给乙和丙,那么对于甲来说,就会产生两个事务,事务 A 及事务 B 。这两个事务分别查询余额,并进行扣款操作:

事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400)
commit;
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400?)
commit;

根据前面可重复读的特性,事务 B 读到的 amount 应该是 500 ,那么减去 100 后,还会是 400 吗?如果是 400 ,那么不是会造成数据不一致(实际转出了两笔 100 ,应该还剩 300 )。

结果是否定的,事务 B 最后读到的 amount 应该是 300 。

在事务中,执行普通 select 查询之后,会创建快照,后面再执行相同的 select 语句时,查询的其实是前面生成的快照。这也就是为什么会有可重复读。

而如果执行:

select * from table where ? lock in share mode; 
select * from table where ? for update; 
insert into table values ();  
update table set ? where ?;  
delete from table where ?;

会执行当前读,获取最新数据。

回到前面的问题,如果事务 B 执行 amount - 100 操作,会触发当前读,读取事务 A 提交后的数据,也就是 400,在此基础上执行 -100 操作,最终 amount 变成 300。

实际上的流程应该如下:

事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400)
commit;
select amount from account where id = 1; (amount = 500,快照读)
update account set amount = amount -100 where id = 1;(触发当前读,amount 为 400 -100)
select amount from account where id = 1; (amount = 300)
commit;

还有一种情况,假如事务 A 和事务 B 并发修改数据但还未提交,那么会怎么样呢?

事务A事务B
start transaction;start transaction;
select amount from account where id = 1; (amount = 500)
select amount from account where id = 1; (amount = 500)
update account set amount = amount -100 where id = 1;
update account set amount = amount -100 where id = 1;
select amount from account where id = 1; (amount = 400?)
select amount from account where id = 1; (amount = 400?)
commit;
commit;

由于事务 A 和事务 B 都没有提交,不能触发当前读,那么 amount 最终会是多少呢?会是 400 吗?

答案是 300,了解数据库锁机制的话,就不会有这种困惑了。

事务 A 提交前,会一直持有排他锁(具体是行锁还是表锁,要看查询条件有没有走索引),此时事务 B 更新是会阻塞的。也就是说,只有事务 A 提交,或回滚之后,事务 B 才能获得排它锁,从而进行更新奖品的操作。

五、总结

Mysql 的默认隔离级别为可重复读 RR(repeatable-read)。

**隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。**因此在开发中需要根据实际情况来进行取舍。

MySQL 的存储引擎是 InnoDB ,通过 MVCC(多版本并发控制) 来实现快照读和当前读,有兴趣的可以参考https://blog.youkuaiyun.com/whoamiyang/article/details/51901888

内容概要:《2024年中国城市低空经济发展指数报告》由36氪研究院发布,指出低空经济作为新质生产力的代表,已成为中国经济新的增长点。报告从发展环境、资金投入、创新能力、基础支撑和发展成效五个维构建了综合指数评价体系,评估了全国重点城市的低空经济发展状况。北京和深圳在总指数中名列前茅,分别以91.26和84.53的得分领先,展现出强大的资金投入、创新能力和基础支撑。低空经济主要涉及无人机、eVTOL(电动垂直起降飞行器)和直升机等产品,广泛应用于农业、物流、交通、应急救援等领域。政策支持、市场需求和技术进步共同推动了低空经济的快速发展,预计到2026年市场规模将突破万亿元。 适用人群:对低空经济发展感兴趣的政策制定者、投资者、企业和研究人员。 使用场景及目标:①了解低空经济的定义、分类和发展驱动力;②掌握低空经济的主要应用场景和市场规模预测;③评估各城市在低空经济发展中的表现和潜力;④为政策制定、投资决策和企业发展提供参考依据。 其他说明:报告强调了政策监管、产业生态建设和区域融合错位的重要性,提出了加强法律法规建设、人才储备和基础设施建设等建议。低空经济正加速向网络化、智能化、规模化和集聚化方向发展,各地应找准自身比较优势,实现差异化发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值