事务

事务的起源

数据库存储的数据是现实中的映射。
每⼀个操作都相当于现实世界中的⼀次状态转换。
我们应该让某些数据库操作符合现实世界中状态转换的规则

数据库的特性

原子性(Atomicity)

现实世界中转账操作是⼀个不可分割的操作,也就是说要么压根⼉就没转,要么转账成功,不能存在中间的状态,也就是转了⼀半的这种情况。设计数据库的⼤叔们把这种要么全做,要么全不做的规则称之为原⼦性。但是在现实世界中的⼀个不可分割的操作却可能对应着数据库世界若⼲条不同的操作,数据库中的⼀条操作也可能被分解成若⼲个步骤(⽐如先修改缓存⻚,之后再刷新到磁盘等),最要命的是在任何⼀个可能的时间都可能发⽣意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚⾄是直接断电之类的)⽽使操作执⾏不下去

隔离性(Isolation)

现实世界中的两次状态转换应该是互不影响的.
所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以 原⼦性 的⽅式执⾏完成,⽽且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为 隔离性 。这时设计数据库的⼤叔们就需要采取⼀些措施来让访问相同数据(上例中的A账户和B账户)的不同状态转换(上例中的 T1 和 T2 )对应的数据库操作的执⾏顺序有⼀定规律

一致性(Consistency)

如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是⼀致的,或者说符合 ⼀致性 的。 数据一致性 还有状态一致性。

持久性(Durability)

当现实世界的⼀个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的⼤叔们称为 持久性
当把现实世界的状态转换映射到数据库世界时, 持久性 意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发⽣了什么事故,本次转换造成的影响都不应该被丢失掉(要不然猫爷还是会被砍死)。

事务的概念

现实世界状态转换过程中需要遵守的4个特性,我们把 原⼦性 ( Atomicity )、 隔离性 ( Isolation )、 ⼀致性 ( Consistency )和持久性 ( Durability )这四个词对应的英⽂单词⾸字⺟提取出来就是 A 、 I 、 C 、 D ,稍微变换⼀下顺序可以组成⼀个完整的英⽂单词: ACID 。
把需要保证 原⼦性 、 隔离性 、 ⼀致性 和 持久性 的⼀个或多个数据库操作称之为⼀个 事务 (英⽂名是: transaction )。
图片说明
图片说明
图片说明

redo 日志

图片说明

redo 日志格式

图片说明
图片说明
如果只是修改还好如果进行了删除和插入,很多数据都是联动的,很多数据都会有变化和更新

把⼀条记录插⼊到⼀个⻚⾯时需要更改的地⽅⾮常多。所以发明了新的redo日志方式
图片说明
图片说明
图片说明

redo⽇志会把事务在执⾏过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。为了节省redo⽇志占⽤的存储空间⼤⼩,inoodb对redo⽇志中的某些数据还可能进⾏压缩处理

Mini-Transaction

以组的形式写⼊redo⽇志

图片说明
我们插入数据的时候如果空闲空间重组,就可以足够容纳一条待插入的记录,插入,然后记录日志就好,但是如果空间不够可能涉及到一些页分裂进行操作复制创建操作就变多了,就会产生很多的redo日志,记录修改了哪里。
图片说明 图片说明
设计 InnoDB 的⼤叔们认为向某个索引对应的 B+ 树中插⼊⼀条记录的这个过程必须是原⼦的,不能说插了⼀半之后就停⽌了。⽐⽅说在悲观插⼊过程中,新的⻚⾯已经分配好了,数据也复制过去了,新的记录也插⼊到⻚⾯中了,可是没有向内节点中插⼊⼀条 ⽬录项记录 ,这个插⼊过程就是不完整的,这样会形成⼀棵不正确的 B+ 树。我们知道 redo ⽇志是为了在系统奔溃重启时恢复崩溃前的状态,如果在悲观插⼊的过程中只记录了⼀部分 redo ⽇志,那么在系统奔溃重启时会将索引对应的 B+ 树恢复成⼀种不正确的状态,这是设计 InnoDB 的⼤叔们所不能忍受的。所以他们规定在执⾏这些需要保证原⼦性的操作时必须以 组 的形式来记录的 redo ⽇志,在进⾏系统奔溃重启恢复时,针对某个组中的 redo ⽇志,要么把全部的⽇志都恢复掉,要么⼀条也不恢复
图片说明
图片说明
图片说明

redo日志写入过程

图片说明
图片说明
图片说明
图片说明
图片说明
图片说明
图片说明
不同的事务可能是并发执⾏的,所以 T1 、 T2 之间的 mtr 可能是交替执⾏的。每当⼀个 mtr 执⾏完成时,伴随该 mtr ⽣成的⼀组 redo ⽇志就需要被复制到 log buffer中,也就是说不同事务的 mtr 可能是交替写⼊ log buffer 的。
图片说明
不同的 mtr 产⽣的⼀组 redo ⽇志占⽤的存储空间可能不⼀样,有的 mtr 产⽣的 redo ⽇志量很少,⽐如 mtr_t1_1 、 mtr_t2_1 就被放到同⼀个block中存储,有的 mtr 产⽣的 redo ⽇志量⾮常⼤,⽐如 mtr_t1_2 产⽣的 redo ⽇志甚⾄占⽤了3个block来存储。

说过的话就⼀定要办到 —— redo ⽇志

redo⽇志⽂件

redo⽇志刷盘时机

图片说明

redo 日志文件组

图片说明
图片说明
图片说明
图片说明
图片说明

Log Sequeue Number

图片说明
图片说明

每⼀组由mtr⽣成的redo⽇志都有⼀个唯⼀的LSN值与其对应,LSN值越⼩,说明redo⽇志产⽣的越早。
图片说明
当有新的 redo ⽇志写⼊到 log buffer 时,⾸先 lsn 的值会增⻓,但 flushed_to_disk_lsn 不变,随后随着不断有 log buff

后悔了怎么办 —— undo ⽇志

图片说明

为了回滚会进行记录undo日志,做了什么修改全部都记录下来,把回滚所需要的数据都记录下来,由于select 并不会修改任何用户记录,所以在进行查询操作执行时,并不需要进行记录相应的undo日志,不同类型的操作产生的undo日志的格式也是不同的。

事务的id

图片说明
无论是只读事务还是读写事务,只有遇到修改的时候才会分配事务id
图片说明
那个相当于系统id,分配给一个事务以后,自己就会进行自增的操作。

trx_id

聚簇索引的记录除了会保存完整的⽤户数据以外,⽽且还会⾃动添加名为trx_id、roll_pointer的隐藏列,如果
⽤户没有在表中定义主键以及UNIQUE键,还会⾃动添加⼀个名为row_id的隐藏列
图片说明
其中的 trx_id 列其实还蛮好理解的,就是某个对这个聚簇索引记录做改动的语句所在的事务对应的 事务id ⽽已(此处的改动可以是 INSERT 、 DELETE 、 UPDATE 操 作)。
图片说明
图片说明
undo no 在⼀个事务中是从 0 开始递增的,也就是说只要事务没提交,每⽣成⼀条 undo⽇志 ,那么该条⽇志的 undo no 就增1。
如果记录中的主键只包含⼀个列,那么在类型为 TRX_UNDO_INSERT_REC 的 undo⽇志 中只需要把该列占⽤的存储空间⼤⼩和真实值记录下来,如果记录中的主键包含多个列,那么每个列占⽤的存储空间⼤⼩和对应的真实值都需要记录下来(图中的 len 就代表列占⽤的存储空间⼤⼩, value 就代表列的真实值)。
图片说明
图片说明
图片说明
roll_pointer 本质就是⼀个指针,指向记录对应的undo⽇志
图片说明
图片说明
图片说明
图片说明
图片说明
从上边的描述中我们也可以看出来,在删除语句所在的事务提交之前,只会经历 阶段⼀ ,也就是 delete mark 阶段(提交之后我们就不⽤回滚了,所以只需考虑对删除操作的 阶段⼀ 做的影响进⾏回滚)。设计 InnoDB 的⼤叔为此设计了⼀种称之为 TRX_UNDO_DEL_MARK_REC 类型的 undo⽇志 ,它的完整结构如下图所示:
图片说明
图片说明

删除⼀条记录

DELETE FROM undo_demo WHERE id = 1;
这个 delete mark 操作对应的 undo⽇志 的结构就是这样:
图片说明
图片说明
图片说明
图片说明
图片说明
图片说明
图片说明
图片说明

FIL_PAGE_UNDO_LOG⻚⾯

表空间其实是由许许多多的⻚⾯构成的,⻚⾯默认⼤⼩为 16KB 。这些⻚⾯有不同的类型,⽐如类型为FIL_PAGE_INDEX 的⻚⾯⽤于存储聚簇索引以及⼆级索引,类型为 FIL_PAGE_TYPE_FSP_HDR 的⻚⾯⽤于存储表空间头部信息的,还有其他各种类型的⻚⾯,其中有⼀种称之为FIL_PAGE_UNDO_LOG 类型的⻚⾯是专⻔⽤来存储 undo⽇志 的,这种类型的⻚⾯的通⽤结构如下图所示(以默认的 16KB ⼤⼩为例):
图片说明
上图中的 File Header 和 File Trailer 是各种⻚⾯都有的通⽤结构,我们前边唠叨过很多遍了,这⾥就不赘述了(忘记了的可以到讲述数据⻚结构或者表空间的章节中查看)。 Undo Page Header 是 Undo⻚⾯ 所特有的,我们来看⼀下它的结构:
图片说明