mysql核心原理第3篇__InnoDB事务原理

一、事务概念及特性

事务是由一系列对数据库中数据进行访问与更新的操作所组成的一个程序执行逻辑序列。这些操作要么全都执行,要么全都不执行,是一个不可分割的工作单元。

比如我们进行银行转账1万元,要进行两个原子动作:动作一:先从客户A账号扣除1万元;动作二:然后在客户B账号上增加1万元。这两个动作一定要一起完成,不管服务器发生停电,断网还是其它故障。 关于事务的详细信息参考我另外一篇文章事务、分布式事务原理及实现

事务是InnoDB最重要的特性,事务需要满足四大特性。

  • 原子性(Atomicity)原子性确保事务作为一个不可分割的工作单位,要么全部执行,要么全部不执行。
  • 一致性(Consistency)一致性保证事务完成后,数据库从一个一致性的状态转变到另一个一致性的状态。
  • 隔离性(Isolation)隔离性防止多个事务并发执行时相互干扰。
  • 持久性(Durability)持久性确保一旦事务提交,其对数据所做的任何更改都将永久保存。

二、持久性保障_redo log(重做日志)

InnoDB持久性是通过redo log(重做日志)来实现的,在数据写入更新时,仅通过WAL机制顺序写入磁盘一条日志,同时更新Buffer Pool内存缓冲中的页信息即认为写成功。而不是直接写磁盘数据页,避免了产生随机写拖慢性能。最终写磁盘依赖于上节中讲到的IO写线程异步刷脏页数据实现。

类似的机制在Hbase,MetaQ,OceanBase,Hologress中都有采用。

三、原子性保障_undo log(回滚日志)

undo log是逻辑日志,它记录的是数据修改前的版本,而不是数据页的物理修改。这意味着undo log可以记录每一行数据的修改历史,并允许InnoDB在需要时回滚到任何一个历史版本。

undo log分为两大类:Insert Undo和Update Undo。

  • Insert Undo:在插入新记录时产生,用于记录新插入记录的主键信息。当需要回滚插入操作时,InnoDB可以利用这些信息删除聚簇索引和二级索引上的相应记录。
  • Update Undo:除了Insert Undo外的所有Undo Log都属于Update Undo,详细来说包含TRX_UNDO_DEL_MARK_REC、TRX_UNDO_UPD_EXIST_REC、TRX_UNDO_UPD_DEL_REC等类型。它们用于记录更新或删除操作前的数据状态,以便在需要时回滚这些操作。

四、mysql锁机制

锁的分类

1. 全局锁

  • 定义:全局锁是一种用于保护整个数据库实例的锁,是MySQL中最高级别的锁。
  • 使用场景:通常在数据库备份、恢复或执行某些维护任务时使用。备份时之所以要加全局锁就是为了保障备份数据库数据的一致性,避免备份事务未提交的脏数据。
  • 特点:全局锁会锁定整个数据库实例,其他会话无法对数据库进行写操作,但可以进行读操作。全局锁具有全局性、独占性和高优先级的特点。
  • 语法:使用FLUSH TABLES WITH READ LOCK命令来开启全局锁。

2. 表锁

  • 定义:表锁是一种用于控制并发访问数据库表的锁。
  • 类型:表锁分为共享锁(S锁)和排他锁(X锁)。共享锁允许多个会话同时读取表中的数据,但不能进行写操作;排他锁则只有一个会话能够获取,可以进行读写操作。
  • 使用场景:表锁通常用于对表进行DDL操作(如ALTER TABLE)或需要操作整张表的情况。表锁也是为了实现数据表更新操作时数据的一致性的,避免DLL过程中产生脏数据。
  • 语法:使用LOCK TABLES tableName READ/WRITE命令来加锁,使用UNLOCK TABLES命令来释放锁。

3. 行锁

  • 定义:行锁是指针对数据表中的某一行进行加锁,其他事务需要访问该行时就需要等待锁释放。
  • 类型:行锁也可以分为共享锁和排他锁。在行锁存在的情况下,其他事务不能对同一行进行写操作(对于共享锁,其他事务可以读取但不能修改;对于排他锁,其他事务既不能读取也不能修改)。
  • 使用场景:行锁用于提高并发性,减少锁冲突。MySQL的InnoDB引擎支持行级锁。行锁也是为了实现多个事务数据更新同一行数据时的一致性设计的。
  • 实现方式:InnoDB的行级锁是通过在索引上设置锁来实现的。

特殊类型的锁

  1. 意向锁:意向锁是一种用于处理并发访问的锁机制,主要用于保护共享资源的访问。它分为意向共享锁(IS锁)和意向排他锁(IX锁)。意向锁可以减少锁的竞争情况,提高并发性能。
  2. 元数据锁(MDL):元数据锁是数据库中一种特殊类型的锁,用于保护数据库的元数据。元数据是描述数据库的数据结构及其属性的信息,包括表、列、索引等对象的定义和属性。元数据锁能够确保在多用户并发访问数据库时,对元数据的修改操作能够按照适当的顺序进行。
  3. 自增锁:确保自增字段在并发插入时能够生成唯一的序列号。
  4. 间隙锁:锁定一个范围,但不包括范围内的记录,主要用于防止事务执行过程中,其它事务在索引间隙中插入数据,产生幻读。
  5. 临键锁:锁定一个范围,并且锁定记录本身,主要用于防止相邻记录插入。
  6. 外键锁:确保外键约束的数据一致性。
  7. 二级索引锁:锁定包含二级索引的列,确保索引数据的一致性。

锁的实现和冲突

  • 实现方式:MySQL的锁实现方式可以分为基于表锁和基于行锁两种。基于表锁的锁机制最简单,它是对整个表进行锁定;而基于行锁的锁机制则更加细粒度,它只锁定需要访问的数据行。
  • 锁冲突:在MySQL锁机制中,不同的锁之间存在不同的冲突关系。当某一个事务申请加锁时,会判断该锁与已经存在的锁是否存在冲突。如果存在冲突,则需要等待已经存在的锁释放后才能申请该锁。常见的锁冲突包括共享锁和排他锁之间的冲突、行级锁和表级锁之间的冲突等。

锁机制的应用和优化

  • 应用:在MySQL中,锁机制被广泛应用于各种并发场景,如电子商务网站的订单处理、银行账户的交易记录查询等。通过合理使用锁机制,可以确保数据的一致性和完整性,同时提高系统的并发性能。
  • 优化:为了避免死锁和提高系统性能,可以采取一些优化措施,如避免长事务和大事务、缩小事务范围、设置合适的锁超时时间等。此外,还可以根据具体的业务需求选择合适的锁粒度和实现方式。

五、事务隔离级别

我们在使用关系数据库的时候,需要我们权衡业务并发量,业务数据隔离诉求两个核心要素,来决定多个事务之间采用什么样的隔离级别。

读未提交(READ UNCOMMITTED)

  • 允许事务读取未被其他事务提交的修改。
  • 可能导致脏读(Dirty Read):一个事务读取到另一个事务未提交的数据,若该未提交事务回滚,则读取到的数据是无效的。
  • 隔离性最差,并发性高,但数据一致性难以保证。

读已提交(READ COMMITTED)

  • 允许事务读取已被其他事务提交的修改。
  • 解决了脏读问题,但可能出现不可重复读(Nonrepeatable Read):在同一个事务内,多次读取同一数据集合时,由于其他事务的提交,导致每次读取结果可能不同。
  • 适用于大多数应用场景,能够较好地平衡数据一致性和并发性。阿里DB默认采用这种隔离级别。

可重复读(REPEATABLE READ)

  • 确保在同一事务内,多次读取同一数据的结果是一致的。
  • 解决了脏读和不可重复读问题,但可能出现幻读(Phantom Read):一个事务读取到另一个事务新增的数据。
  • MySQL的默认事务隔离级别。通过多版本并发控制(MVCC)等技术实现,但可能引入锁机制以解决幻读问题。

串行化(SERIALIZABLE)

  • 强制事务串行执行,以避免脏读、不可重复读和幻读问题。
  • 隔离性最高,但并发性能最差,因为事务必须按顺序执行。
  • 适用于对数据一致性要求极高,但对并发性要求不高的场景。

六、隔离性&一致性保障_mvcc(多版本并发控制)

mvcc主要是为了实现mysql多个事务并发操作同一数据行时,确保数据是按照预期的隔离级别处理的,不会因为并发出现不可预知的脏数据。这部分非常复杂,需要先了解事务的一些基本概念。

Current Read(当前读)

当前读是指读取最新的数据记录,并且读取时会加锁,以确保读取到的数据不会被其他事务修改。

主要用于需要确保数据一致性的场景,如UPDATE、DELETE、SELECT FOR UPDATE、SELECT LOCK IN SHARE MODE等操作。

Snapshot Read(快照读)

快照读是指读取数据的一个快照版本,而不是最新的数据记录。快照读通常不会加锁,或者只会加共享锁,以避免阻塞其他事务。

在InnoDB存储引擎中,使用READ COMMITTEDREPEATABLE READ隔离级别进行普通SELECT查询时,通常就是快照读。

mvcc概念(多版本并发控制)

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制,是一种用于提高数据库并发性能的技术。

MVCC通过维护数据的多个版本来避免读写冲突,使得读操作无需阻塞写操作,写操作也不会影响读操作。它的主要目的是提高数据库的并发性能,并更好地处理读-写冲突,即使在存在读写冲突的情况下,也能做到不加锁,从而降低死锁的风险。

mvcc实现依赖的3个隐藏字段

实现mvcc需要依赖表的三个隐藏字段,我们创建InnoDB每一张表时除了常规定义的字段,还会包含三个隐藏字段DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。

  • DB_TRX_ID,代表最近一次提交事务的ID
  • DB_ROLL_PTR,指向该行数据的上一个版本的指针。当对某行数据进行修改时,InnoDB不会直接覆盖原始数据,而是会在新的位置写入修改后的数据,并更新DB_ROLL_PTR字段,使其指向修改前的数据版本。这样,如果事务需要回滚,InnoDB就可以通过DB_ROLL_PTR字段找到之前的数据版本,并将其恢复为当前版本
  • DB_ROW_ID,隐藏主键,用于标识数据行的物理存储位置。当表没有唯一主键才会生成该隐藏字段。

mvcc undo log(版本链)

以下表举例。

id

age

name

30

10

A30

假设有以下几个事务对上表中同一条id=30记录进行修改:

事务2

事务3

事务4

事务5

开始事务

开始事务

开始事务

开始事务

修改id=30记录,age改为3

查看id=30的记录

提交事务

修改id=30记录,name改为A3

查询id=30记录

提交事务

修改id=30记录,age改为10

查询id=30记录

提交事务

那么在修改过程中,undo log中会依据事务执行顺序,生成一条记录版本链表,链表的头部是最新的旧记录,链表的尾部是最早的旧记录。

  • 事务2修改age=3后undo log日志会记录1条修改前age=30的记录,表记录中DB_ROLL_PTR指针指向undo log日志修改前记录0x00001,方便事务回滚。

  • 事务3修改name=A3后undo log日志会在记录1条修改前name=A30的undo log日志记录0x00002,表记录中DB_ROLL_PTR指针指向0x00002修改前的日志记录。0x00002日志记录中DB_ROLL_PTR则指向更早版本日志记录0x00001。

  • 事务4修改age=10后undo log日志会在记录1条修改前age=3的undo log日志记录0x00003,表记录中DB_ROLL_PTR指针指向0x00003修改前的日志记录。0x00003日志记录中DB_ROLL_PTR则指向更早版本日志记录0x00002。

mvcc undo log ReadView(读视图)

ReadView作用

主要用来确保在事务读取数据时能够看到一个一致性的快照,具体是两大作用:

  • 一致性读:当事务进行SELECT操作时,如果启用了一致性读(例如,通过普通的SELECT语句,而不是LOCK IN SHARE MODE或FOR UPDATE),InnoDB会使用ReadView来确保读取到的数据是一个一致性的快照。这意味着,即使其他事务在读取过程中对数据进行了修改,读取事务也不会看到这些修改。
  • 避免幻读:ReadView还用于解决幻读问题。幻读是指在同一个事务中,两次相同的查询返回了不同的结果集,通常是因为其他事务在两次查询之间插入了新的数据行。通过ReadView,InnoDB可以确保在事务的生命周期内,相同的查询总是返回相同的结果集。

ReadView内容

当事务执行一致性读时,InnoDB会为该事务创建一个ReadView。ReadView包含了以下信息:

  • m_ids:当前活跃的事务ID列表。这些事务是在ReadView创建时仍然活跃的事务。
  • min_trx_id:ReadView创建时最小的活跃事务ID(不包括创建ReadView的事务本身)。
  • max_trx_id:ReadView创建时下一个将要分配的事务ID(即,当前最大事务ID + 1)。
  • creator_trx_id:创建ReadView的事务ID。

生成ReadView的时机

不同的事务隔离级别,生成ReadView的时机不同。

  • Read Uncommitted(读未提交)事务可以读取其他事务尚未提交的数据。这种情况事务直接读取记录的最新版本,不用考虑其他事务的提交状态。因此不需要生成ReadView来判断数据的可见性
  • Read Committed(读已提交读)每次执行快照读(SELECT查询)时都会生成一个新的ReadView。从而确保读取到的是其他事务已经提交的数据。
  • Repeatable Read(可重复读)ReadView只在事务的第一次快照读时生成,并且整个事务中后续的快照读都会共享这个相同的ReadView。
  • Serializable(序列化)事务会按照某种顺序依次执行,完全避免所有的并发问题。不需要ReadView。

ReadView匹配数据快照规则

当事务SELECT某一行的数据时,InnoDB会使用ReadView来判断该行数据的可见性,详细判断规则如下:

Read Committed(读已提交)下快照匹配例子

以上一节例子中事务5为例,假设隔离级别为Read Committed(读已提交),每次执行快照读(SELECT查询)时都会生成一个新的ReadView,生成ReadView的时机以及最终读取的快照值例子:

事务2

事务3

事务4

事务5

开始事务

开始事务

开始事务

开始事务

修改id=30记录,age改为3

查看id=30的记录

提交事务

修改id=30记录,name改为A3

查询id=30记录

生成ReadView: mids: [3,4,5],

min_trx_id: 3, max_trx_id: 6,

creator_trx_id: 5

匹配到快照:0x00002 即事务2提交后的快照。

提交事务

修改id=30记录,age改为10

查询id=30记录

生成ReadView: mids: [4,5],

min_trx_id: 4, max_trx_id: 6

creator_trx_id: 5

匹配到快照:0x00003 即事务3提交后的快照。

提交事务

Repeatable Read(可重复读)下快照匹配例子

以上一节例子中事务5为例,假设隔离级别为Repeatable Read(可重复读),ReadView只在事务的第一次快照读时生成,并且整个事务中后续的快照读都会共享这个相同的ReadView。生成ReadView的时机以及最终读取的快照值例子:

事务2

事务3

事务4

事务5

开始事务

开始事务

开始事务

开始事务

修改id=30记录,age改为3

查看id=30的记录

提交事务

修改id=30记录,name改为A3

查询id=30记录

生成ReadView: mids: [3,4,5],

min_trx_id: 3, max_trx_id: 6,

creator_trx_id: 5

匹配到快照:0x00002 即事务2提交后的快照。

提交事务

修改id=30记录,age改为10

查询id=30记录

ReadView复用之前生成值

匹配到快照:0x00002 即事务2提交后的快照。

提交事务

七、事务原理总结

原子性-undolog(回滚日志)

undo log是逻辑日志,记录了数据修改前的版本,记录每一行数据的修改历史,InnoDB可以在需要时回滚到任何一个历史版本,比如断电,断网等极端情况。从而保证了事务的原子性。

持久性-redolog(重做日志)

InnoDB持久性是通过redo log(重做日志)来实现的,在数据写入更新时,仅通过WAL机制顺序写入磁盘一条日志,同时更新Buffer Pool内存缓冲中的数据页、索引页信息即认为写成功。而不是直接写磁盘数据页,避免了产生随机写拖慢性能。最终写磁盘依赖于上节中讲到的IO写线程异步刷脏页数据实现。如果出现故障再通过WAL恢复数据。

一致性-redolog+undolog

InnoDB的一致性是通过redo log(重做日志)和undo log(回滚日志)一起配合来实现的。如果事务执行过程出现异常则通过undo log(回滚日志)回滚到之前的版本。如果数据库运行过程中途断电,进程被杀导致数据未写入数据及索引页,则通过redo log(重做日志)恢复。

隔离性-锁+mvcc

  • 锁机制:InnoDB使用锁机制来避免并发事务之间的数据不一致。它支持行级锁和表级锁两种锁机制,以及意向锁和间隙锁等高级锁机制。这些锁机制可以确保在事务执行过程中,数据不会被其他事务非法修改或读取。
  • 多版本并发控制(MVCC):MVCC是InnoDB实现事务隔离性的另一种重要机制。它通过为每个事务创建一个独立的数据库视图,避免读写冲突和脏读等并发问题。在MVCC中,每个数据行都会有多个版本,每个版本都有一个唯一的时间戳或事务ID。这些版本用于跟踪数据的历史变化。事务在读取数据时,会读取数据的快照版本(即事务开始时的数据状态),而不是当前最新的数据版本。这样可以避免读操作和写操作之间的冲突,提高并发性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值