MYSQL事务的通俗理解

1.前言

        关于mysql事务的相关概念,即事务ACID四大特性、数据库事务隔离级别,描述很简单,但是需要真正理解还需要了解其实现原理,所以本文基于一篇优秀的事务讲解文章,结合自己的理解,对相关内容理解过程做一个记录;原文贴在最后,如果对原文有理解障碍的朋友,这篇文档应该能对你有所启发。

2.事务

2.1什么是事务?

        MYSQL事务是一组操作逻辑单元,是MYSQL数据库操作的最小执行单元

2.2事务有哪些特性?(ACID)

(1)原子性(Atomicity)

        事务是不可再分的操作逻辑单元,一组事务中包含的crud要么全部执行,要么全不执行。一组事务中的某一个操作失败时,相关数据将回滚到该事务开始前的状态,不存在部分执行的情况。

(2)一致性(Consistency)

        一致性是指数据库管理系统(DBMS)在执行一系列操作时,确保数据的状态从一个一致的状态转换到另一个一致的状态;总的来说,事务的一致性是保证数据在任何时刻都处于一种合理、预期的状态,而不受并发操作、系统故障等因素的影响。数据库管理系统通过事务的原子性、隔离性和持久性来实现数据的一致性。

(3)隔离性(Isolation)

        多个事务并发执行时,每个事务的执行都应该与其他事务隔离开来,彼此之间互不干扰,让它们看起来像是顺序执行一样。这可以防止并发执行时出现数据不一致或者不可预料的情况。

(4)持久性(Durability)

        一旦事务提交,其所做的修改应该永久保存在数据库中,任何情况下都不会丢失。

2.2事务解决什么问题?

(1)防止数据库中某条数据在并发操作下的数据冲突,即避免一条数据数据同时被多个线程任务操作时,操作结果不满足预期。

(2)防止系统故障导致的数据操作不满足预期,及数据操作过程中可能包含多个步骤,避免其中一个或多个步骤出现系统或业务异常时,操作结果不满足预期。

2.3多事务并发情况下可能出现哪些问题

        MySQL服务器支持多个Client进行连接,每个连接进行数据库操作时会开启一个事务,多个事务可能操作同一条数据,意味着存在多事务并发情况;事务并发情况下,可能会出现脏读、幻读和不可重复读的问题:

(1)脏读

        一个事务读取到另一个线程未提交的修改结果;即事务B开启(begin)后对某条数据进行了修改,事务B结束(commit)前,事务A对读取到事务B未提交的数据,则事务A此时获取的数据为脏数据,若事务B针对这条数据的修改最后并未提交,事务A将脏数据用于后续操作,则可能出现不满足一致性的结果,这个现象成为脏读。

(2)不可重复读

        不可重复读指的是在同一个事务中,两次读取同一行数据,但得到的结果不同。例如在事务A的执行过程中,事务B修改了同一行数据并提交了,导致事务A的第二次读取与第一次读取结果不一致。这种情况下,同一个事务中的两次读取发生了矛盾,称为不可重复读。

(3)幻读

        幻读指的是在同一个事务中,两次查询读取到的结果集数量不一致。具体来说,当一个事务在执行相同的查询两次时,如果其他并发事务在这两次查询之间插入、更新或删除了符合查询条件的数据,导致查询结果集合的条目数不一致,称为幻读。

2.4MYSQL数据库如何避免多事务并发产生违背一致性结果

        MYSQL数据库是通过设置事务的隔离级别,从而不同程度的避免多事务并发问题;

(1)读未提交

        当MYSQL数据库事务隔离级别被设置为读未提交时,则事务可以读取到其他事务未提交的修改结果,则可能产生脏读、不可重复读和幻读的问题。

(2)读已提交

        当MYSQL事务隔离级别被设置为读已提交,则事务无法读取到其他事务还未提交的结果,可以避免脏读问题,但是如果一个事务中的两次相同查询分别读取其他事务提交前和提交后的数据,仍然会出现不可重复读或者幻读的问题。

(3)可重复读

        当MYSQL事务隔离级别设置为可重复读,则事务开始后,事务会通过行级锁锁定其涉及的数据行,确保参与事务的数据行始终保持事务开始时的状态,确保事务中相同查询结果也相同,从而避免不可重复读问题;但是由于使用的是行级锁而非范围锁,所以在其他事务插入数据时,新插入的数据如果满足当前事务查询条件,则会出现在查询结果集中,从而产生幻读的问题。

(4)串行化

        当MYSQL事务隔离级别设置为串行化,则事务开启后,会使用范围锁【Next-Key锁(间隙锁+行级锁)/间隙锁】(@Q:InnoDB行锁和范围锁原理是什么?)对某个索引范围内的数据进行加锁,方式其他事务在加锁范围内新增数据或修改范围内的数据,从而避免脏读、幻读以及不可重复读问题。

3.MVCC(Multi Version Concurrency Control)

3.1MVCC是什么以及为什么需要MVCC?

        多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制概念或模型,根据前文,如果需要在解决多并发下的不可重复读、幻读等问题,需要借助行锁或范围锁等锁机制,加锁意味着操作阻塞,影响数据操作性能。

        针对读-写操作的冲突问题,引入MVCC概念,MVCC可以有效地通过在读取时不加锁,而是读取数据的历史版本来避免读取操作被写入操作所阻塞。这样可以实现读取操作的并发性,提高数据库的性能。具体实现原理将在后文中说明。

        MVCC仅仅解决读-写冲突问题,针对写-写冲突问题,仍然需要借助范围锁等操作。


3.2MVCC是如何解决读写冲突的?

        MYSQL中的数据读取,分为当前读和快照读两种方式,其中当前读则是通过加锁的方式,保证获取数据的一致性;快照读是在事务开始前为事务创建数据快照,在数据的历史版本中去判断其对当前事务的可见性的方式保证当前事务数据一致性,避免了加锁及阻塞,提升并发性能。

        普通select(不包括 select ... lock in share mode; select ... for update;)都属于快照查的方式,数据修改的操作(update、insert、delete) 都是采用 当前读的模式。若数据库隔离级别设置为串行化,则快照读将退化为当前度

        其中快照读就是MYSQL的InnoDB数据引擎对MVCC模型的实现。

3.3快照读的具体实现是怎样的?

3.3.1数据行的4个隐式字段

       首先,mysql在产生新的数据行时,会默认生成4个隐式字段:

        (1)数据唯一ID:6byte,字段名称为db_row_id,隐式的自增主键,若表未指定逐渐时,系统会自动根据该字段为表生成一个聚簇索引(主键索引)

        (2)最近操作(修改、插入)数据的事务ID:6byte,字段名称为db_trx_id,最后一次操作(修改、插入)该行数据的事务ID;每个事务被开启时都将生成一个自增长的ID(@Q当前事务ID分配机制是什么?),事务进行数据修改时会将其ID记录到操作数据行的本数据字段中。

        (3)指向上个版本数据地址的指针:7byte,字段名称为DB_ROLL_PTR,数据每次被操作前都会记录上个版本完整数据到undo日志中,回滚指针则指向undo日志中上个版本,用于后续数据回滚等操作。

        (4)数据是否被删除:1byte,字段名称为DELETED_BIT,执行数据删除时并未真正将数据删除,而是修改该数据标识,后续有专门的任务对标识为删除的数据进行彻底清除。(delete是事务性操作,遵循上述机制;TRUNCATE 是非事务性操作,会直接将数据删除而非修改DELETED_BIT标识,执行速度更快,没有事务日志,无法回滚。

3.3.2  UNDO LOG

        上一节中提到的undo日志,InnoDB将支持回滚的历史数据记录在undo log中,针对不同操作及历史数据记录方式如下:

  •         SELECT: 不修改数据记录,无需存储UNDO日志
  •         INSERT: 记录新增数据的ID,涉及回滚操作时直接删除ID对应的数据记录
  •         UPDATE: 记录完整数据,涉及回滚操作时直接将数据完整还原
  •         DELETE: 记录完整数据,涉及回滚操作时直接将数据完整还原(@Q:被标识为DELETED_BIT=true的记录是如何真正被数据库清除的?)

        不同事务对数据记录的修改日志记录最终会形成一条数据链表,链首时第一条操作记录,链尾是最新的修改记录。(@Q:该链表中的数据清理机制是怎样的?)

3.3.3 读视图(Read View)

        事务进行快照的的同时,数据库系统会为该事务生成一个读视图,这个读视图中包含以下内容:

(1)创建读视图的当前事务ID(creator_trx_id 

(2)创建读视图时,正在活跃的事务最小ID(min_trx_id )

(3)创建读视图时,下一个应该被分配的事务ID,即当前最大事务ID(不管是否活跃)+1(max_trx_id

(4)创建读视图时,正在活跃的事务ID列表(m_ids 

        读视图的作用是为了判断undo log中的历史数据列表对读视图对应事务的可见性,其针对数据行对应UNDO LOG中的每一条记录,由版本从新到旧逐一进行以下条件判断,任一条件成立则退出判断,表示满足以下任一条件的历史数据对本事务可见(trx_id表示UNDO LOG中数据记录的最后修改事务ID):

        1)trx_id == creator_trx_id(本条件成立代表trx_id 对应数据记录的最后修改事务为本事务,该数据记录对本事务可见)

        2)trx_id < min_trx_id(本条件成立则代表trx_id对应的数据记录在本事务开启时已经被提交了,该数据记录对本事务可见 )

        3)trx_id > =min_trx_id  and trx_id <=max_trx_id and trx_id  not in m_ids (本条件代表trx_id对应的数据记录事务在本事务开启之前已经被提交,则该记录数据对本事务可见)

3.4MVCC是如何影响不同隔离级别下的数据集结果的?

(1)读未提交

        直接读取最新数据,不管提交与否,可能产生脏读、不可重复读、幻读,不受MVCC机制影响

(2)串行化

        采用加锁的方式保证数据一致性,不受MVCC机制影响

(3)读已提交

        每次查询开始前都会重新创建Read View,及两次查询操作的Read View记录可能不一样,则会产生不可重复读或者幻读问题

(4)可重复读

        事务开启后仅仅创建一次Read View,后续数据查询都使用同一个Read View,可以解决不可重复读问题,并一定程度上解决幻读问题(@Q:哪些情况下仍然会出现幻读问题)

4.参考

结合图文一起搞懂MySQL事务、MVCC、ReadView! - 知乎

5.遗留问题

(1)InnoDB行锁和范围锁原理是什么?

(2)事务开启时当前事务ID分配机制是什么?

(3)UNDO LOG历史版本数据链表的数据清理机制是怎样的?

(4)被标识为DELETED_BIT=true的记录是如何真正被数据库清除的?

(5)MVCC机制下的可重复读事务隔离等级,哪些情况下仍然会出现幻读问题?

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值