数据库系统原理学习笔记

目录

  • 一、事务
    • 概念
    • ACID
  • 二、并发一致性问题
    • 丢失修改
    • 读脏数据
    • 不可重复读
    • 幻影读
  • 三、四种隔离级别
    • 未提交读
    • 提交读
    • 可重复读
    • 可串行化
  • 四、多版本并发控制
    • 基本思想
    • 版本号
    • Undo日志
    • ReadView

一、事务

概念

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

ACID

1.原子性(Atomicity)

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

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

2.一致性(Consistency)

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

3.隔离性(Isolation)

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

4.持久性(Durability)

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

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

二、并发一致性问题

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

丢失修改

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

读脏数据

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

不可重复读

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

幻影读

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

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

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

  • 封锁粒度:行级锁、表级锁
  • 封锁类型:读/写锁(S/X)、意向锁(IS/IX)
  • 封锁协议:三级封锁协议(描述的是什么时候加什么样的锁以实现什么样的隔离级别)

三、四种隔离级别

未提交读

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

提交读

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

可重复读

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

可串行化

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

四、多版本并发控制

多版本并发控制(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 字段,用于标记是否被删除。
数据库系统原理学习笔记_第6张图片

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 找到下一个快照,再进行上面的判断。

你可能感兴趣的:(数据库)