0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

SQL事物机制特性

马哥Linux运维 来源:马哥Linux运维 2023-05-19 09:28 次阅读

1. 事务

事务是指一个或者多个数据库操作,要么全部没有执行,要么全部成功执行。

中途失败需要回滚到指定状态,全部执行成功需要确保持久保存在数据库中。

事务拥有四个特性,习惯上被称之为ACID特性。

2. ACID特性

为了更直观的解释ACID特性,下面先说明A, B, C之间互相转账的过程。

假设A有10元,B有15元,C有8元

A给B转账5元,操作记为T1。

T1: read(A), A=A-5, write(A), read(B), B=B+5, write(B)。

T1操作的大体流程,先读取A到账户余额,将A的账户余额扣减5元后再写入数据库中,

读取B的账户余额,将B的账户余额增加5元后再写入到数据库中。

同时,C给B转账4元,操作记为T2。

T2: read(C), C=C-4, write(C), read(B), B=B+4, write(B)

T1操作的大体流程,先读取C的账户余额,将C的账户余额扣减4元后再写入数据库中,

读取B的账户余额,将B的账户余额增加4元后再写入到数据库中。

2.1 原子性(Atomicity)

事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行,是一个最小执行单元,不可分割。

A给B转账5元的操作T1是包含多个读写操作,这些操作要么全部执行,要么全部不执行。

假设由于断电等意外事件,导致T1只执行了部分操作,如T1:read(A), A=A-5, write(A)

这就会导致A凭空少了5元,并且B没有收到A转的5元,

因此事务需要保证保证在事务执行过程中出现错误时,将已经执行的操作“撤销”,恢复到原始状态。

2.2 一致性(Consistency)

事务应确保数据库的状态从一个一致状态转变为另一个一致状态。

假设A有10元,B有15元,C有8元,不管A, B, C之间如何进行转账(没有其他人参与),三个点账户总余额一定是33元,而不会是其它值。

2.3 隔离性(Isolation)

多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

A给B转账5元,同时C给B转账4元,这个两个事务应该是互相隔离的,互不影响。

最终A余额为5元,C余额为4元,B总共收到两次转账,余额应该为24元。

假设T1, T2可以交叉执行,如下图所示。最终结果看起来B只收到了A的5元转账,余额为20元。

230456be-f5b6-11ed-90ce-dac502259ad0.png

2.4 持久性(Durability)

已被提交的事务对数据库的修改应该永久保存在数据库中。

MySQL操作一般是先写入缓存,满足指定条件后才将缓存更新到磁盘

磁盘写操作相当耗时,而且同一个事务可能修改多个数据页面,而且可能执行页面中一个字节的数据。

因此每次数据库提交执行缓存刷新磁盘操作不太合理,

MySQL设计人员通过redo日志来持久化最小量的数据来达到相同的效果。

3. redo日志

为了保证事务的持久性,在事务提交动作完成之前,需要把该事务修改所有页面都刷新到磁盘,但是存在以下问题

随机I/O比较慢,一个事务可能修改到多个页面,这些页面在磁盘中可能不相邻,可能需要多次长距离移动磁盘读写磁头。

刷新完整的数据页面较慢,一个事务也可能值修改页面中一个字节,却要同步整个页面到磁盘上。

为了解决上述到问题,InnoDB设计了redo日志,把事务修改的内容采用特定的格式按顺序保存到磁盘上,

即使在系统崩溃之后,按照redo日志重新更改数据页进行数据恢复即可。

3.1 redo日志格式

redo日志通用格式如下图所示

23215c28-f5b6-11ed-90ce-dac502259ad0.png

type,redo日志类型,通过定义不同类型可以达到节省空间的目的

space id,表空间id

page number,页号

data,redo日志具体内容

往表中插入一条记录,可能产生多条redo日志,因为可能产生聚簇索引对应B+树页面的分裂操作,

可能需要性申请数据页,金额可能要修改各种段、区的统计信息等,

最终插入一条记录可能产生多条redo日志,这些日志是不可分割的,

在崩溃恢复时,也是将这一组日志作为不可分割的整体来处理,

类似的,将一组不可分割的redo日志称为Mini-Transaction,即MTR

3.2 redo日志缓冲区

为了避免频繁的磁盘IO,并不是每生成一条redo日志就同步到磁盘上。

而是先将redo日志放到缓冲区,在特定时机刷新到磁盘。

redo日志缓冲区页面结构如下图所示。

2334501c-f5b6-11ed-90ce-dac502259ad0.png

redo日志是以MTR为单位写入到redo日志缓冲区的,redo日志缓冲区是有若干个512B大小的block构成的一片连续的内存空间,

InnoDB引擎使用lsn(log sequence number)来记录系统当前有多少redo日志写入到缓冲区

3.3 redo日志文件

3.3.1 flush链表中的lsn

InnoDB会将lsn相关信息写入到flush链表中,进而方便判断哪些redo日志文件可以被重复使用,

因为只要脏页被刷新到磁盘,相应的redo日志内容就没有存在的意义了,而且redo日志文件大小也有限。

Buffer Pool中页面会在控制块中记录页面的修改信息

oldest_modification: 第一次修改Buffer Pool中某个页面时,将MTR开始时的lsn写入该变量

newest_modification: 每次修改Buffer Pool中某个页面时,将MTR结束时的lsn写入该变量

flush链表与oldest_modification(o_m)和newest_modification(n_m)的关系如下图所示。

23478f24-f5b6-11ed-90ce-dac502259ad0.png

flush链表的基节点start指针出发,flush链表的脏页时按照第一次修改发生的时间倒序排列的,也就是按照oldest_modification代表的lsn值倒叙排列,

当页面被多次更新时,会更新对应页面的newest_modification变量的值。

当页面1被属性到磁盘,从页面2的控制块可以看出,lsn低于8916的redo日志可以被覆盖,系统会将8916赋值到redo文件的checkpoint_lsn的操作。

InnoDB将检查flush链表最小的oldest_modification的lsn值称为checkpoint操作。

3.3.2 redo日志文件格式

磁盘上存在多个redo日志文件,会被循环使用,这一组redo日志文件称为redo日志文件组。

235f18c4-f5b6-11ed-90ce-dac502259ad0.png

和redo日志缓冲区一样,redo日志文件也是由若干个512B构成的block组成

其中,redo日志文件的头2048个字节用于存储一些管理信息,系统会将checkpoint操作得到的checkpoint_lsn赋值到checkpoint1的checkpoint_lsn上。

崩溃恢复会从checkpoint_lsn在日志文件组中对应的偏移量开始。

除了前面阐述的checkpoint,redo日志刷盘时机还包括

redo日志缓存不足时

事务提交时

后台线程周期性刷盘

正常关闭服务器

3.3.3 奔溃恢复

当遇到异常情况导致服务器挂掉,在重启时可以根据redo日志文件恢复到奔溃前的状态。

InnoDB从redo日志文件组的第一个文件的checkpoint信息,然后从checkpoint_lsn在日志文件组中对应的偏移量开始,

一直扫描日志文件中的,直到某个block的写入量的值不等于512,根据redo日志格式将修改的内容恢复到奔溃前状态。

4. undo日志

在事务执行过程中可能遇到各种错误,导致中途就结束事务了,但是在遇到错误退出前,可能修改多个行记录,

但是为了保证事务的原子性,需要将数据恢复到事务开启前,这个恢复过程就称为回滚。

为了回滚,就需要将事务修改的内容记录下来,包括插入的行记录、修改行记录的内容、删除的行记录,

保存事务执行过程中修改内容的东西称为undo日志。

4.1 undo日志格式

4.1.1 聚簇索引行结构

InnoDB会将聚簇索引行结构如下图所示

238252da-f5b6-11ed-90ce-dac502259ad0.png

InnoDB会将聚簇索引行结构补充trx_id和roll_pointer两个隐藏列

trx_id: 一个事务某次对某条聚簇索引记录进行改动时,都会把该事务的事务ID赋值给trx_id,事务ID是单调递增的

roll_pointer: 每次对某条聚簇索引进行改动时,都会把旧版本写入到undo日志中,并以聚簇索引为起点构成一个从最新到最旧的单向链表结构,这个链表就称为版本链

roll_pointer结构如下图所示

2398a562-f5b6-11ed-90ce-dac502259ad0.png

is_insert, 表示该指针指向的undo日志是否是TRX_UNDO_INSERT大类的undo日志

resg id, 表示该指针指向的undo日志的回滚段编号

page number, 表示该指针指向的undo日志所在页面的页号

offset, 表示该指针指向的undo日志在页面中的偏移量

4.1.2 插入操作

如果需要回滚插入操作,只需要将插入的记录删除即可,

因此在记录undo日志时,只需要记录插入的记录的主键信息即可,通过主键能找到唯一的记录

插入操作的对应的undo日志类型为TRX_UNDO_INSERT_REC,结构如下图所示

23a757b0-f5b6-11ed-90ce-dac502259ad0.png

undo type, 即TRX_UNDO_INSERT_REC

undo no, 事务执行过程中,每生成一条undo日志,undo no就增加1,且从0开始

table id, 该undo日志对应的记录所在表的table id

4.1.3 删除操作

在事务中执行删除操作,会将记录的deleted_flag标识为值为1,但该记录依然在正常记录链表,并没有移动到垃圾记录链表,这个过程称为delete mark。

在事务提交后,才把该记录从正常记录链表挪到垃圾记录链表

删除操作产生TRX_UNDO_DEL_MARK_REC类型的日志,结构如下图所示,

23b6f33c-f5b6-11ed-90ce-dac502259ad0.png

在对一条记录进行delete mark操作前,将该记录的trx_id和roll_pointer的旧值保存到undo日志的trx_id和roll_pointer变量中。

假设一个事务对某条记录先更新再删除,这样就能通过TRX_UNDO_DEL_MARK_REC找到更新的undo日志。

4.2.4 更新操作

更新操作的场景较复杂,InnoDB将其分为更新主键和不更新主键两种场景。

不更新主键

更新的每个列在更新前后占用的存储空间不变,则可以进行就地更新

更新前后占用存储空间有变,需要将旧记录从聚簇索引删除,再创建一条新的记录

更新主键,旧记录执行delete mark操作,创建新纪录插入聚簇索引

针对以上各种情况,InnnoDB设计了对应的undo日志格式,限于篇幅这里就不展开说明。

4.2 undo日志页面

和InnoDB普通页面结构类型,undo日志页面结构及页面链表如下图所示。

23ccf9d4-f5b6-11ed-90ce-dac502259ad0.png

一个undo日志页面只能存储一种类型,InnoDB将undo日志分为两大类,

TRX_UNDO_INSERT,由insert语句产生undo日志,或者update语句更新主键也会产生该类型的undo日志

TRX_UNDO_UPDATE,除了TRX_UNDO_INSERT类型的undo日志,其它类型的undo日志都属于这个大类

InnoDB对临时表和普通表产生的undo日志分开记录,因此一个事务最多可能需要4个undo页面链表。

4.3 回滚

同一个时刻,可能存在多个事务在执行,为了更好的管理undo页面链表,

InnoDB设计了Rollback Segment Header页面用于存放各个Undo页面链表的第一个undo页面的页号,即undo slot。

在奔溃恢复时,需要将未提交事务的修改回滚掉,通过undo slot找到undo页面链表,

通过判断undo页面链表的Undo Log SegmentHeader的TRX_UNDO_STATE属性值,

如果为TRX_UNDO_ACTIVE,则进一步通过undo链表最后一个页面的Undo Log Header中找到该事务对应的事务ID,

然后通过undo日志内容将该事务修改的内容全部撤销,从而保证事务的原子性。

5. 事务隔离级别和MVCC

5.1 常见一致性问题

脏写(Dirty Write)

一个事务修改了另外一个未提交事务修改过的数据。

脏读(Dirty Read)

一个事务读取了另外一个未提交事务修改过的数据。

不可重复读(Non-repeatable Read)

一个事务修改了另一个未提交事务读取的数据。

幻读(Phantom)

一个事务根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合搜索条件的记录,

再次以相同条件查询,前后两次结果不一致。

5.2 事务隔离级别

SQL标准中定义了四种隔离级别

读未提交(Read Uncommitted), 读已提交(Read Committed), 可重复读(Repeatable Read), 串行化(Serializable)。

不同隔离级别对应的可能和不可能发生的一致性问题如下图所示。

其中脏写问题是不允许发生的。

23e3a30a-f5b6-11ed-90ce-dac502259ad0.png

5.3 MVCC

5.3.1 版本链

InnnoDB存储引擎的聚簇索引的版本链如下图所示

假设在某个时刻,事务321、315、301对某条记录进行Updata操作后,形成的版本链如下图所示。其中事务301对这条记录更新了两次

23f8b2d6-f5b6-11ed-90ce-dac502259ad0.png

5.3.2 MVCC和ReadView

Multi-Version Concurrency Control, 即多版本并发控制,

多版本并发控制机制利用聚簇索引的版本链来控制并发事务访问相同记录时的行为,从而解决脏读和不可重复读的不一致性问题。

ReadView,即一致性视图,通过这个视图可以判断版本链的某个版本是否可被当前事务访问,中ReadView包含以下4个比较重要的数据

creator_trx_id,生成该ReadView的事务对应的事务ID

m_ids,在生成ReadView时当前系统中活跃的事务ID列表

min_trx_id,m_ids的最小值

max_trx_id,生成ReadView时,系统的下一个事务ID值。

判断是否可见的规则

如果被访问版本的trx_id值与ReadView中的creator_trx_id值相同,说明是当前事务在访问自己修改过的记录,该版本可以被当前事务访问

如果被访问版本的trx_id值小于ReadView中min_trx_id值,说明该版本在当前事务生成ReadView前已经提交,因此该版本可以被访问

如果被访问版本的trx_id值不小于ReadView中的max_trx_id值,说明该版本的事务在当前事务生成ReadView后启动,因此该版本不可访问

如果被访问版本的trx_id值在ReadView的min_trx_id和max_trx_id之间

如果trx_id在m_ids列表中,说明ReadView生成时该版本的事务依然活跃,因此该版本不可访问

如果trx_id不在在m_ids列表中,说明ReadView生成时该版本的事务已经提交,因此该版本可以被访问

顺着版本链重复上述操作,直到找到可以访问的版本,或者到达版本链末尾。如果版本链最后一个版本依然不可见,则查询结果为记录不存在

在读已提交和可重复读隔离级别下,ReadView生成的时机有所不同,

读已提交在每一次进行普通Select操作前都会生成一个ReadView,确保了读取到都是已提交事务的数据

可重复读只在第一次进行普通Select操作前生产一个ReadView, 之后查询操作都重复使用这个ReadView,保证了同一个事务内不同时间读到相同数据

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • SQL
    SQL
    +关注

    关注

    1

    文章

    738

    浏览量

    43462
  • 磁盘
    +关注

    关注

    1

    文章

    338

    浏览量

    24883
  • 数据库
    +关注

    关注

    7

    文章

    3591

    浏览量

    63373

原文标题:一文读懂SQL事物机制

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    labview 的ActiveX 运行机制是什么?

    各位大神,本人正在用labview进行数据库的查询等操作,网上也有例子,想问下如果想理解下它的ActiveX机制和有关ADO编程模型的,有这方面的资料吗?还是需要看SQL
    发表于 09-05 18:37

    Phoenix版本的特性及和普通MySQL SQL用法的差异

    Phoenix使用注意事项以及跟标准sql的不同
    发表于 08-07 12:54

    源码Spark SQL的分区特性

    源码Spark SQL 分区特性第一弹
    发表于 04-28 07:36

    为什么要动态sql语句?

    为什么要动态sql语句?因为动态sql语句能够提供一些比较友好的机制1、可以使得一些在编译过程中无法获得完整的sql语句,在程序执行阶段动态的获得。2、支持动态组装
    发表于 12-20 06:00

    SQL参考手册

    SQL 合计函数使用 SQL 合计函数 你可以确定数据组的各种统计。你可以把这些函数用于查询和合计表达式,条件是在具备 SQL特性的 QueryDef对象中或在创建基于
    发表于 12-26 14:09 39次下载

    SQL Server系统概述课程

    特性、新增强功能;SQL Server 2005安装的软硬件需求及安装过程;SQL Server 2005
    发表于 04-14 15:54 0次下载

    打造SQL Server2000的安全策略教程

    打造SQL Server2000的安全策略教程  Microsoft建立了一种既灵活又强大的安全管理机制,它能够对用户访问SQL Server服务器系统
    发表于 01-27 11:42 506次阅读

    连接SQL的远程数据库同步机制

    从数据库连接驱动中捕获SQL的方法,并提出一致性校验算法进行数据验证。实验结果表明,该机制能提高远程数据库同步效率,同时支持异构数据库同步,可为大数据时代多数据中心和灾备系统建设提供技术支持。
    发表于 01-24 17:11 1次下载
    连接<b class='flag-5'>SQL</b>的远程数据库同步<b class='flag-5'>机制</b>

    SQL后悔药,SQL性能优化和SQL规范优雅

    每一个好习惯都是一笔财富,本文基于MySQL,分SQL后悔药, SQL性能优化,SQL规范优雅三个方向,分享写SQL的21个好习惯,谢谢阅读,加油哈~ 1. 写完
    的头像 发表于 11-14 09:54 1602次阅读

    一文掌握MyBatis的动态SQL使用与原理

    摘要:使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
    的头像 发表于 01-06 11:27 662次阅读

    9SQL4952-9SQL4954-9SQL4958 系列数据表

    9SQL4952-9SQL4954-9SQL4958 系列数据表
    发表于 03-13 20:20 0次下载
    9<b class='flag-5'>SQL4952-9SQL4954-9SQL</b>4958 系列数据表

    计算机视觉中的注意力机制

    在自然语言处理、图像识别及语音识别等各种不同类型的机器学习任务中。注意力机制本质上与人类对外界事物的观察机制相似。通常来说,人们在观察外界事物的时候,首先会比较关注比较倾向于观察
    发表于 05-22 09:46 0次下载
    计算机视觉中的注意力<b class='flag-5'>机制</b>

    动态Sql介绍

    动态Sql介绍 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记
    的头像 发表于 05-31 09:34 1099次阅读
    动态<b class='flag-5'>Sql</b>介绍

    9SQL4952-9SQL4954-9SQL4958 系列数据表

    9SQL4952-9SQL4954-9SQL4958 系列数据表
    发表于 07-05 19:04 0次下载
    9<b class='flag-5'>SQL4952-9SQL4954-9SQL</b>4958 系列数据表

    MyBatis动态sql是什么?MyBatis动态SQL最全教程

    动态 SQL 是 MyBatis 的强大特性之一。在 JDBC 或其它类似的框架中,开发人员通常需要手动拼接 SQL 语句。根据不同的条件拼接 SQL 语句是一件极其痛苦的工作。
    的头像 发表于 08-10 10:18 589次阅读