详解mysql事务,事务并发安全问题的复现以及大事务的优化

您所在的位置:网站首页 事务并发执行时,每个事务不必关心其他事务 详解mysql事务,事务并发安全问题的复现以及大事务的优化

详解mysql事务,事务并发安全问题的复现以及大事务的优化

2024-07-08 12:33| 来源: 网络整理| 查看: 265

好文推荐: 2.5万字详解23种设计模式 springboot 实现延时队列(超级实用) 2.5万字讲解DDD领域驱动设计

文章目录 1. 事务定义2. 事务特性(ACID)3. 事务并发问题4. 事务隔离级别5. 基础命令6. 脏读复现7. 不可重复读复现8. 幻读复现8.1 幻读疑问8.2 MVCC8.3 快照读8.4 当前读8.5 幻读再次复现 9. 大事务问题总结 在这里插入图片描述

1. 事务定义

MySQL事务是指一系列数据库操作(例如INSERT、UPDATE、DELETE等)被作为一个单独的执行单元来执行的过程。事务可以确保数据库在并发环境下的数据一致性和完整性,并支持原子性、一致性、隔离性和持久性的特性(也称为ACID特性)。

2. 事务特性(ACID)

原子性(Atomicity):事务是一个原子操作单元,要么全部执行成功,要么全部失败回滚。如果事务中的任何一个操作失败,则整个事务将被回滚到初始状态。

一致性(Consistency):事务在执行之前和执行之后,数据库的状态必须保持一致。这意味着事务将从一个一致的状态转换到另一个一致的状态,确保数据的完整性。

隔离性(Isolation):每个事务的执行都应该与其他事务相互隔离,使它们彼此独立。事务的隔离性确保并发执行的事务不会相互干扰,从而防止数据损坏或不一致。

持久性(Durability):一旦事务提交成功,其所做的更改将永久保存在数据库中,即使在系统故障或重启后也能够恢复。持久性确保事务的结果能够长期存储,以便供后续读取和使用。

3. 事务并发问题

两个事务都是读操作不会有并发问题,只有在读写或者写写场景下,就有并发问题了,后面会通过实战复现一下四种问题。

脏读:在读读场景下,一个事务读取了另一个未提交(或已回滚)事务的数据。如果读取到的数据最终不会被提交,那么本次读取就是脏读。

不可重复读:在读写(update)场景下,同一个事务内,多次读取同一行数据时,得到了不同的结果。这是由于其他事务在读取期间修改了数据造成的。

幻读:在读写(insert)场景下,一个事务读到另一个事务已提交的新插入的数据。其实这样的说法并不准确,严谨来说是在repeatable read隔离级别下,只有在当前读的情况下,才会有幻读问题,在快照读的情况下,是没有幻读问题的。什么是当前读,什么是快照读,下文会结合实例一一讲解。

更新丢失:在写写(update)场景下,两个事务同时读取同一行数据,并且同时进行修改。由于事务隔离级别的不同或执行顺序的不同,其中一个事务的修改可能会覆盖另一个事务的修改,导致更新丢失。

4. 事务隔离级别

事务隔离级别是数据库管理系统(DBMS)提供的一种机制,用于控制并发事务之间的可见性和相互影响程度。MySQL 支持四种事务隔离级别:

read uncommitted:未提交读 ①最低的隔离级别。 ②事务可以读取其他未提交事务的未提交数据。 ③会导致脏读、不可重复读和幻读。read committed:已提交读 ①事务只能读取已提交事务的数据,不能读取未提交事务的数据。 ②解决了脏读问题,但导致不可重复读和幻读。repeatable read:可重复读,MySQL默认的隔离级别。 ①事务在开始时读取一个快照,然后在事务结束之前都使用该快照。 ②其他事务的插入和更新会在事务结束前被阻塞。 ③解决了脏读和不可重复读的问题,但仍存在幻读现象,幻读这里画重点,后面会讲到。serializable:序列化 ①最高的隔离级别。 ②所有事务按顺序执行,不允许并发,所以不建议使用,否则MySQL没有一点并发能力了。 ③提供了最强的隔离保证,完全防止脏读、不可重复读和幻读,但可能会导致性能下降,因为并发性受到限制。 事务隔离级别 脏读 不可重复读 幻读 read uncommitted: 否 否 否 read committed : 是 否 否 repeatable read: 是 是 否 serializable: 是 是 是 5. 基础命令 查看当前会话的事务隔离级别 SELECT @@tx_isolation; 设置当前会话的隔离级别 set session transaction isolation level read uncommitted; set session transaction isolation level read committed; set session transaction isolation level repeatable read; set session transaction isolation level serializable; 开启一个事务 BEGIN; 提交一个事务 COMMIT; 回滚一个事务 ROLLBACK; 6. 脏读复现

将事务隔离级别设置为:read uncommitted

事务A:查询数据 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level read uncommitted; -- 开启事务 begin; select * from student;

查询结果:在这里插入图片描述

事务B:修改id=1的age为28,但不提交事务 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level read uncommitted; -- 开启事务 begin; update student set age = 28 where id = 1; 事务A:再次查询数据,id=1的数据age改为了19,发生了脏读 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level read uncommitted; -- 开启事务 begin; select * from student;

查询结果:在这里插入图片描述

解决方案:提升隔离级别为:read committed 7. 不可重复读复现

将事务隔离级别设置为:read committed,虽然解决了脏读,但是存在不可重复读

事务A:先查询数据 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level read committed; -- 开启事务 begin; select * from student;

查询结果:在这里插入图片描述

事务B:修改id=1的age为19,并且提交事务 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level read committed; -- 开启事务 begin; update student set age = 19 where id = 1; commit; 事务A:再次查询数据,发生了不可重复读 select @@tx_isolation; #查看当前会话的事务隔离级别 set session transaction isolation level read committed; #设置当前会话的隔离级别 begin; #开启事务 select * from student #查询数据

查询结果:在这里插入图片描述

解决方案:提升事务隔离级别:repeatable read 8. 幻读复现

将事务隔离级别设置为:repeatable read,可以解决脏读和重复读

8.1 幻读疑问 事务A:先查询数据 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; select * from student;

查询结果:在这里插入图片描述2. 事务B:插入一条数据,并提交事务

-- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; INSERT INTO student (id, name, age) VALUES(3, '刘备', 38); commit; 事务A:再次查询数据,大家可能会疑问,并没有出现事务B插入的数据,没有发生预想的幻读现象,这是为什么呢?这里就涉及到几个概念 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; select * from student;

在这里插入图片描述

8.2 MVCC MVCC(Multi-Version Concurrency Control):多版本并发控制,是一种用于处理并发事务的数据库管理系统技术。MVCC通过在数据库中维护多个版本的数据来实现事务隔离,从而允许多个事务并发执行而不会发生冲突。用来解决数据库的并发读写的应用场景问题(一个事务在读,一个事务在写,保证数据一致性),采用无锁的方式来解决,这个是最牛逼的。MVCC主要用于支持可重复读(Repeatable Read)隔离级别,并在许多现代数据库系统中得到广泛应用,包括MySQL和PostgreSQL。 8.3 快照读

快照读是MVCC的核心特性之一。

当事务启动时,它创建一个事务快照,以捕获数据库中的当前状态。在整个事务期间,事务只能看到在其启动时存在的数据版本。快照读适用于可重复读(Repeatable Read)隔离级别,它确保在整个事务期间数据保持一致性。读取一致性:事务在其整个生命周期内都会看到一个一致的数据快照,无论其他事务是否在同时进行。无锁读取,MVCC通常不需要使用显式锁来实现事务隔离,因为每个事务都有自己的数据快照,不需要锁定其他事务正在读取的数据。所有的select查询语句在可重复读、读已提交的隔离级别下都是快照读。 8.4 当前读 当前读是一种更加实时的读取方式。与快照读不同,当前读不使用已存在的快照,而是始终访问数据库的最新版本。当前读适用于读取最新数据的需求,通常用于读取而不是写入操作。但需要注意,当前读可能会与其他事务的写操作发生冲突,因此需要格外小心以避免并发问题。除了select操作都会触发(update,delete,insert,select…lock in share mode,select…for update)都是当前读。lock in share mode是共享锁,for update是排他锁,相关mysql各种锁的问题,关注小编后续发布文章! 8.5 幻读再次复现 事务A:当前读查询数据 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; select * from student for update; 事务B:插入一条数据 -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; INSERT INTO student (id, name, age) VALUES(3, '刘备', 38); commit; 事务A:再次查询,来了来了它来了,幻读问题出现了!!! -- 查看当前会话的事务隔离级别 select @@tx_isolation; -- 设置当前会话的隔离级别 set session transaction isolation level repeatable read; -- 开启事务 begin; select * from student for update;

结果如下:在这里插入图片描述

9. 大事务问题

大事务问题通常涉及对数据库中大量数据进行处理的事务,这些事务可能导致性能下降、锁冲突或事务执行时间过长。

假设有一个下单的应用场景,每当用户提交订单时,系统会执行以下操作: ①创建一个新订单记录 ②从用户购物车中删除已购买的商品 ③更新商品库存。 现在,假设有一天,你的网站举办了一个促销活动,吸引了大量用户同时下订单。这导致了大量的 “订单处理” 事务同时运行,每个事务都要对订单和库存进行操作。

大量订单处理事务同时运行可能会导致以下问题: ①锁冲突:多个事务同时尝试修改相同的订单或商品库存,导致锁定和等待。 ②性能下降:大量的数据库操作可能导致数据库性能急剧下降。 ③事务超时:事务执行时间过长,导致超时问题。 解决目标就是,缩短事务执行时间,提高性能

解决方法: ①事务拆分: 将大事务拆分成多个小事务,每个小事务只处理一个订单或一组相关订单。 这可以减小每个事务的范围,减少锁冲突和提高并发性。 ②使用乐观锁: 使用乐观锁机制来处理商品库存更新。每个事务在更新库存之前检查库存是否足够,如果不够就回滚事务。 这可以避免大规模的锁冲突。 ③使用消息队列: 将订单处理任务放入消息队列,然后由后台任务异步处理。 这将减轻数据库的压力,并允许系统在非高峰时段处理订单。 ⑤性能优化: 优化数据库表结构和索引以提高查询性能。 使用数据库缓存来减少重复查询。 ⑥定期清理历史数据: 定期清理订单和日志数据,以减少数据库负担。

总结 我们在实际项目中,一般都会使用mysql默认的隔离级别, repeatable read可重复读可重复读解决了事务并发安全问题的脏读,不可重复读当前读的幻读问题MySQL是怎么解决呢,其实MySQL是通过next-key lock 临键锁来避免幻读问题的,相关mysql各种锁的问题,关注小编后续发布文章!

如果看到这里,说明你喜欢这篇文章,请关注和点赞小编。关注【微信公众号微信】搜索【老板再来一杯时光】回复【进群】即可进入无广告交流群!回复【java】即可获取【java基础经典面试】一份!

好文推荐: 2.5万字详解23种设计模式 springboot 实现延时队列(超级实用) 2.5万字讲解DDD领域驱动设计



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3