欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据库系统原理学习笔记

程序员文章站 2024-03-15 17:49:54
...

一、事务

概念

事务是指满足ACID特性的一组操作。

ACID

1.原子性(Atomicity)

事务被视为不可分割的最小单元,事务包含的所有操作要么全部提交成功,要么全部失败回滚。

回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

2.一致性(Consistency)

数据库在事务执行前后保持一致性状态,在一致性状态下,所有事务对同一个数据的读取结果都是相同的。

3.隔离性(Isolation)

事务所做的修改在最终提交之前,对其它事务是不可见的。

4.持久性(Durability)

事务一旦被提交,其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改!

二、并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题

丢失修改

一个事务的更新操作被另外一个事务的更新操作替换,如下图所示:
数据库系统原理学习笔记

读脏数据

指当前事务可以读到其他事务未提交的数据,如下图:
数据库系统原理学习笔记

不可重复读

指在一个事务内多次读取同一数据集合,在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改,导致同一事务中两次读取的数据可能不一致,如下图:
数据库系统原理学习笔记

幻影读

幻影读本质上也属于不可重复读的情况,其强调的是事务多次读取某个范围的数据,而另一事务在该范围内插入/删除了的数据,使得同一事务两次读取结果不同,如下图:
数据库系统原理学习笔记

注:不可重复读和幻影读的区别在于,不可重复读强调的是数据被修改了,而幻影读强调的是范围内数据新增或减少了。

产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过*来实现,但是*操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

三、四种隔离级别

未提交读

指事务中的修改,即使没有提交,对其他事务也是可见的。

提交读

事务在最终提交之前,对其他事务是不可见的,也就是说一个事务只能读取到已经提交的事务所做的修改。

可重复读

同一事务中多次读取同一数据的结果是一样的。

可串行化

所有事务串行执行,事务间互不干扰,因此不会出现并发一致性问题。
数据库系统原理学习笔记

四、多版本并发控制

多版本并发控制(MVCC)是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读两种隔离级别。未提交读隔离级别总是读取最新的数据行,要求很低,无需使用MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现。

基本思想

加锁能解决多个事务同时执行时出现的并发一致性问题,又由于实际场景中读操作往往多于写操作,因此我们引入了读写锁来避免不必要的加锁操作(读/读没有互斥关系)。但加锁方式中,读和写操作仍然是互斥的,MVCC利用多版本的思想,使得读和写操作没有了互斥关系:写操作更新最新的版本快照,而读操作去读旧版本的快照。

在MVCC中事务的修改操作(如delete、insert、update)会为数据行新增一个版本快照。

版本号

  • 系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
  • 事务版本号 TRX_ID :事务开始时的系统版本号。

Undo日志

MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行所有快照连接起来。

例如在 MySQL 创建一个表 t,包含主键 id 和一个字段 x。我们先插入一个数据行,然后对该数据行执行两次更新操作。

INSERT INTO t(id, x) VALUES(1, "a");
UPDATE t SET x="b" WHERE id=1;
UPDATE t SET x="c" WHERE id=1;

因为没有使用 START TRANSACTION 将上面的操作当成一个事务来执行,根据 MySQL 的 AUTOCOMMIT 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
数据库系统原理学习笔记

INSERT、UPDATE、DELETE 操作会创建一个日志,并将事务版本号 TRX_ID 写入。DELETE 可以看成是一个特殊的 UPDATE,还会额外将 DEL 字段设置为 1。

ReadView

MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, …},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。

在进行 SELECT 操作时,根据数据行快照的 TRX_ID 与 TRX_ID_MIN 和 TRX_ID_MAX 之间的关系,从而判断数据行快照是否可以使用:

  • TRX_ID < TRX_ID_MIN,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。

  • TRX_ID > TRX_ID_MAX,表示该数据行快照是在事务启动之后被更改的,因此不可使用。

  • TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX,需要根据隔离级别再进行判断:
    (1)提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
    (2)可重复读:不管 TRX_ID 在不在 TRX_IDs 列表中都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。

在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。

相关标签: 数据库