测试必看:MySQL事务隔离,轻松搞定并发问题

📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:

软件测试工程师简历上如何编写个人信息(一周8个面试)

软件测试工程师简历上如何编写专业技能(一周8个面试)

软件测试工程师简历上如何编写项目经验(一周8个面试)

软件测试工程师简历上如何编写个人荣誉(一周8个面试)

软件测试行情分享(这些都不了解就别贸然冲了.)

软件测试面试重点,搞清楚这些轻松拿到年薪30W+

软件测试面试刷题小程序免费使用(永久使用)


背景与意义

在数据库管理系统中,事务(Transaction)是确保数据一致性和完整性的基本单位,即保证一组数据库操作,要么全部成功,要么全部失败。

事务的ACID特性——原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)——为数据的可靠操作提供了保障。其中,事务的隔离性尤为重要,它确保了并发执行的事务之间互不干扰,从而避免了数据不一致的问题。

然而,在实际应用中,开发者常常会遇到“你改了我看不见”的现象,这实际上正是事务隔离机制在起作用。本文将深入探究MySQL事务隔离的底层实现原理,帮助读者理解这一现象的本质。

关键技术

01 锁机制

锁是实现事务隔离的重要手段之一。

MySQL 使用了多种类型的锁,包括共享锁(Shared Lock)和排他锁(Exclusive Lock)。

共享锁允许多个事务同时读取同一数据资源,而排他锁则在事务修改数据时使用,阻止其他事务对该数据的读写操作。

例如,在一个事务对某行数据执行 SELECT...FOR UPDATE 语句时,会获取该行数据的排他锁,确保在该事务完成修改并提交之前,其他事务不能对该行数据进行修改或读取不一致的数据。

尽管依赖锁机制保证了事务的隔离性,然而单纯的锁机制会降低数据库并发度,为此MySQL通常采用锁+MVCC的策略来保证隔离性与并发度。

02 多版本并发控制(MVCC)

MVCC 是 MySQL 实现事务隔离的核心技术之一。

它通过为每个数据行维护多个版本,使得不同事务在不同时间点看到不同版本的数据。在 MVCC 机制下,事务在读取数据时,根据自身的事务 ID 和数据行的创建版本号、删除版本号等来确定能够看到的数据版本。

这样,即使有其他事务正在修改数据,读取事务也能够读取到在其开始时刻之前已提交的数据版本,从而实现了一定程度的并发读取而不被阻塞,提高了数据库系统的并发性能。

原理介绍与解析

01 事务隔离级别及现象

1、读未提交(Read Uncommitted):

这是最低的事务隔离级别,事务可以读取到其他未提交事务修改的数据。

这种级别下,并发性能最高,但数据的一致性和准确性难以保证,可能会出现脏读(Dirty Read)现象(下图所示,事务2在T5时刻读取到了事务1在T4时刻未提交的修改),即读取到了其他事务未提交的修改数据,而这些数据可能随后被回滚,导致读取到的数据是无效的。

2、读已提交(Read Committed):

事务只能读取到已提交事务修改的数据。相比读未提交级别,它避免了脏读,但可能出现不可重复读(Non - Repeatable Read)问题。

在一个事务内多次读取同一数据行,可能由于其他事务在这期间提交了对该行数据的修改,导致每次读取到的数据不一致(下图中事务2在T2、T4时刻读取数据一致,在事务1提交后的T6时刻读取的数据和T2、T4时刻不一致)

3、可重复读(Repeatable Read):

这是 MySQL 默认的事务隔离级别。在该级别下,事务在执行期间多次读取同一数据行时,会得到相同的数据,即使其他事务对该行数据进行了修改并提交。

这是通过 MVCC 机制实现的,事务开始时确定了其能够看到的数据版本范围,在事务执行期间不会受到其他事务提交的修改的影响。

然而,在可重复读级别下可能会出现幻读(Phantom Read)现象,即事务在执行过程中,按照某个查询条件多次查询数据,发现符合条件的数据行数不一致,这是由于其他事务插入或删除了符合条件的数据行。

4、可串行化(Serializable):

最高的事务隔离级别,事务串行执行,完全避免了并发事务之间的相互影响,保证了数据的强一致性,但并发性能最差。

(下图中事务2查看数据时,事务1无法对数据进行修改;当事务2提交后,事务才能对数据进行修改)

02 MVCC 原理深入解析

在 MVCC 中,每个事务在开始时被分配一个唯一的事务 ID。

对于数据库中的每一行数据,除了存储实际的数据值外,还维护了创建版本号(creation_version)和删除版本号(deletion_version)。

  • 当一个事务插入一行数据时,该行数据的创建版本号被设置为该事务的 ID。

  • 当一个事务更新一行数据时,实际上是插入了一个新的版本,新数据行的创建版本号为当前事务的 ID,而原数据行的删除版本号被设置为当前事务的 ID,表示原数据行在该事务中被删除(逻辑删除)。

  • 当一个事务删除一行数据时,该行数据的删除版本号被设置为当前事务的 ID。

在事务读取数据时,根据事务 ID 和数据行的创建版本号、删除版本号来判断是否可见。

如果数据行的创建版本号小于等于当前事务的 ID,并且删除版本号大于当前事务的 ID 或者未定义(表示数据行未被删除),则该数据行对当前事务可见。

通过这种方式,不同事务在不同时间点能够看到不同版本的数据,实现了事务隔离中的数据读取一致性。

底层实现的分析与探究

01 InnoDB 存储引擎中的事务隔离实现

InnoDB 是 MySQL 的默认存储引擎,其在事务隔离实现方面有着深入的设计。在 InnoDB 中,锁和 MVCC 机制紧密结合。

例如,在可重复读事务隔离级别下,事务在首次读取数据时,会根据 MVCC 机制确定能够看到的数据版本,并获取相应的共享锁(如果只是读取操作)。

如果事务需要对数据进行修改,则会将共享锁升级为排他锁。在事务执行过程中,InnoDB 通过维护一个事务 ID 列表和数据行的版本信息,来判断数据行对不同事务的可见性。

对于锁的管理,InnoDB 采用了多种锁算法,如记录锁(Record Lock)、间隙锁(Gap Lock)和邻键锁(Next - Key Lock)等。

记录锁锁定单个数据行,间隙锁锁定数据行之间的间隙,临键锁则是记录锁和间隙锁的组合,用于防止幻读现象。

在可重复读级别下,当事务执行范围查询(如 SELECT...WHERE...)时,InnoDB 会自动使用临键锁,锁定查询范围内的数据行以及相邻的间隙,确保在事务执行期间,其他事务插入符合查询条件的数据行时会被阻塞,从而避免幻读。

02 事务日志与事务隔离

事务日志在 MySQL 事务隔离实现中也起着重要作用。InnoDB 使用重做日志(Redo Log)和 undo 日志。重做日志用于在数据库发生故障时恢复已提交事务对数据的修改,确保数据的持久性。

undo 日志则用于事务回滚操作,记录了事务修改数据前的原始值。在事务隔离的底层实现中,通过 undo 日志可以获取数据行的历史版本信息,以便在事务读取数据时提供正确的数据版本。

具体逻辑为:unlog通过事务ID标识DB_TRX_ID、数据回滚指针DB_ROLL_PTR、数据行标识DB_ROW_ID三个隐藏字段来实现历史版本信息的记录。如下图所示。

  • 首先事务1在t时刻插入了一条数据,该修改行会维护相关的插入数据信息及隐藏字段信息;

  • 之后事务2在t2时刻(t2>t1)修改更新了同一行数据,此时在undolog日志中会保留这行数据的历史版本,最新版本的DB_ROLL_PTR字段指向这行数据的历史版本;

  • 之后事务3在t3时刻(t3>t2)对同一行数据又做了更新修改,同理将事务2修改的版本存入undolog,同时最新版本的数据行中隐藏字段DB_ROLL_PTR指向事务2修改后的版本。

这样事务修改得到所有的版本通过隐藏字段DB_ROLL_PTR连接形成一个版本链,历史版本记录在undolog中,头节点始终为最新的版本信息。通过控制不同事务读取不同的版本即可实现部分隔离级别。

03 MVCC 原理深入解析

ReadView(读视图)是快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)ID,MVCC在undolog中记录的数据的历史版本信息,通过控制事务读取不同的读视图即能实现隔离级别的控制。

在 MVCC 中,每个事务在开始时被分配一个唯一的事务 ID。对于数据库中的每一行数据,除了存储实际的数据值外,还维护了创建版本号(creation_version)和删除版本号(deletion_version)。

  • 当一个事务插入一行数据时,该行数据的创建版本号被设置为该事务的 ID。

  • 当一个事务更新一行数据时,实际上是插入了一个新的版本,新数据行的创建版本号为当前事务的 ID,而原数据行的删除版本号被设置为当前事务的 ID,表示原数据行在该事务中被删除(逻辑删除)。

  • 当一个事务删除一行数据时,该行数据的删除版本号被设置为当前事务的 ID。

在事务读取数据时,根据事务 ID 和数据行的创建版本号、删除版本号来判断是否可见。如果数据行的创建版本号小于等于当前事务的 ID,并且删除版本号大于当前事务的 ID 或者未定义(表示数据行未被删除),则该数据行对当前事务可见。

通过这种方式,不同事务在不同时间点能够看到不同版本的数据,实现了事务隔离中的数据读取一致性。事务的不同隔离级别也正是通过MVCC机制与锁赖共同实现的,具体而言不同隔离级别的实现逻辑如下:

  • 读未提交: 读不加锁,写加排他锁。

  • 读已提交: 读不加锁,写加排他锁,在事务中每一次执行快照读(不加锁)时生成ReadView。

  • 可重复读: 读不加锁,写加临键锁,在事务中仅第一次执行快照读时生成ReadView,后续复用该ReadView。

  • 串行化:  读加共享锁,写加排他锁。

总结

MySQL 事务隔离是一个复杂而重要的数据库特性,其底层实现涉及锁机制、MVCC、事务日志等多个关键技术。

通过不同的事务隔离级别,MySQL 能够在数据一致性和并发性能之间进行权衡,满足不同应用场景的需求。

深入理解事务隔离的底层实现原理,对于数据库管理员和开发人员来说至关重要。合理设置事务隔离级别、优化查询语句和事务设计,可以有效提高数据库应用的性能和数据的可靠性。

在未来的数据库技术发展中,随着对高并发和大数据量处理需求的不断增长,MySQL 事务隔离机制可能会进一步优化和演进,例如在更高效的锁管理、更智能的 MVCC 实现以及与分布式数据库事务处理的融合等方面,以适应不断变化的数据库应用环境。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值