Skip to content

Commit

Permalink
auto commit
Browse files Browse the repository at this point in the history
  • Loading branch information
CyC2018 committed Dec 8, 2019
1 parent eb11869 commit 827725d
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 80 deletions.
65 changes: 25 additions & 40 deletions docs/notes/数据库系统原理.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
* [可串行化SERIALIZABLE)](#可串行化serializable)
* [多版本并发控制](#五多版本并发控制)
* [基本思想](#基本思想)
* [Undo 日志](#undo-日志)
* [版本号](#版本号)
* [隐藏的列](#隐藏的列)
* [实现过程](#实现过程)
* [Undo 日志](#undo-日志)
* [ReadView](#readview)
* [快照读与当前读](#快照读与当前读)
* [Next-Key Locks](#六next-key-locks)
* [Record Locks](#record-locks)
Expand Down Expand Up @@ -257,65 +256,51 @@ SELECT ... FOR UPDATE;

## 基本思想

在封锁一节中提到加锁能解决多个事务同时执行时出现的并发一致性问题但是加锁操作代价很高并且在实际场景中读多写少所有事务都是只是进行读操作的话就没必要进行加锁
在封锁一节中提到加锁能解决多个事务同时执行时出现的并发一致性问题在实际场景中读操作往往多于写操作因此又引入了读写锁来避免不必要的加锁操作例如读和读没有互斥关系读写锁中读和写操作仍然是互斥的 MVCC 利用了多版本的思想写操作更新最新的版本快照而读操作去读旧版本快照没有互斥关系这一点和 CopyOnWrite 类似

MVCC 的读操作不需要进行加锁并且在可重复读隔离级别下能解决脏读和不可重复读问题它的基本思想是为每个数据行维护多个版本的快照多个事务可以同时去操作这个数据行
MVCC 中事务的修改操作DELETEINSERTUPDATE会为数据行新增一个版本快照

事务的修改操作DELETEINSERTUPDATE会去修改该事务对应版本的快照
脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改在事务进行读取操作时为了解决脏读和不可重复读问题MVCC 规定只能读取已经提交的快照当然一个事务可以读取自身未提交的快照这不算是脏读

## 版本号

脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改在事务在进行读取操作时为了解决脏读和不可重复读问题读取的快照需要满足以下条件快照在该事务开始之后没有被其它事务修改否则会读取到其它事务的未提交的修改操作
- 系统版本号 SYS_ID是一个递增的数字每开始一个新的事务系统版本号就会自动递增
- 事务版本号 TRX_ID事务开始时的系统版本号

## Undo 日志

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

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

```sql
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 机制每个操作都会被当成一个事务来执行所以上面总共涉及到三个事务

<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208012527591.png"/> </div><br>

## 版本号

- 系统版本号是一个递增的数字每开始一个新的事务系统版本号就会自动递增
- 事务版本号事务开始时的系统版本号

## 隐藏的列

MVCC 在每个数据行后面都保存着两个隐藏的列用来存储两个版本号

- 创建版本号指示创建一个数据行的快照时的系统版本号
- 删除版本号如果该快照的删除版本号未定义或删除版本号大于当前事务版本号表示该快照有效

## 实现过程

以下实现过程针对可重复读隔离级别

因为数据行快照的创建版本号是创建数据行快照时的系统版本号系统版本号随着创建事务而递增所以新开始一个事务时这个事务的系统版本号比之前的系统版本号都大也就是比当前所有数据行快照的创建版本号都大
因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行根据 MySQL AUTOCOMMIT 机制每个操作都会被当成一个事务来执行所以上面的操作总共涉及到三个事务快照中除了记录事务版本号 TRX_ID 和操作之外还记录了一个 bit DEL 字段用于标记是否被删除

### 1. SELECT
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208164808217.png"/> </div><br>

多个事务必须读取到同一个数据行的快照并且这个快照是距离现在最近的一个有效快照但是也有例外如果有一个事务正在修改该数据行那么它可以读取事务本身所做的修改而不用和其它事务的读取结果一致
INSERTUPDATEDELETE 操作会创建一个日志并将事务版本号 TRX_ID 写入DELETE 可以看成是一个特殊的 UPDATE还会额外将 DEL 字段设置为 1

把没有对一个数据行做修改的事务称为 TT 所要读取的数据行快照的创建版本号必须小于等于 T 的版本号因为如果大于 T 的版本号那么表示该数据行快照是其它事务的最新修改因此不能去读取它除此之外T 所要读取的数据行快照的删除版本号必须是未定义或者大于 T 的版本号因为如果小于等于 T 的版本号那么表示该数据行快照是已经被删除的不应该去读取它
## ReadView

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

将当前系统版本号作为数据行快照的创建版本号
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208171445674.png"/> </div><br>

### 3. DELETE
在进行 SELECT 操作时根据数据行快照的 TRX_ID TRX_ID_MIN TRX_ID_MAX 之间的关系从而判断数据行快照是否可以使用

将当前系统版本号作为数据行快照的删除版本号
- TRX_ID < TRX_ID_MIN表示该数据行快照时在当前所有未提交事务之前进行更改的因此可以使用

### 4. UPDATE
- TRX_ID > TRX_ID_MAX表示该数据行快照是在事务启动之后被更改的因此不可使用
- TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX需要根据隔离级别再进行判断
- 提交读如果 TRX_ID TRX_IDs 列表中表示该数据行快照对应的事务还未提交则该快照不可使用否则表示已经提交可以使用
- 可重复读都不可以使用因为如果可以使用的话那么其它事务也可以读到这个数据行快照并进行修改那么当前事务再去读这个数据行得到的值就会发生改变也就是出现了不可重复读问题

将当前系统版本号作为更新前的数据行快照的删除版本号并将当前系统版本号作为更新后的数据行快照的创建版本号可以理解为先执行 DELETE 后执行 INSERT
在数据行快照不可使用的情况下需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照再进行上面的判断

## 快照读与当前读

Expand All @@ -329,7 +314,7 @@ SELECT * FROM table ...;

### 2. 当前读

MVCC 其它会对数据库进行修改的操作INSERTUPDATEDELETE需要进行加锁操作从而读取最新的数据
MVCC 其它会对数据库进行修改的操作INSERTUPDATEDELETE需要进行加锁操作从而读取最新的数据可以看到 MVCC 并不是完全不用加锁而只是避免了 SELECT 的加锁操作

```sql
INSERT;
Expand Down
Binary file added notes/pics/image-20191208164808217.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added notes/pics/image-20191208171445674.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 25 additions & 40 deletions notes/数据库系统原理.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
* [可串行化SERIALIZABLE)](#可串行化serializable)
* [多版本并发控制](#五多版本并发控制)
* [基本思想](#基本思想)
* [Undo 日志](#undo-日志)
* [版本号](#版本号)
* [隐藏的列](#隐藏的列)
* [实现过程](#实现过程)
* [Undo 日志](#undo-日志)
* [ReadView](#readview)
* [快照读与当前读](#快照读与当前读)
* [Next-Key Locks](#六next-key-locks)
* [Record Locks](#record-locks)
Expand Down Expand Up @@ -257,65 +256,51 @@ SELECT ... FOR UPDATE;

## 基本思想

在封锁一节中提到加锁能解决多个事务同时执行时出现的并发一致性问题但是加锁操作代价很高并且在实际场景中读多写少所有事务都是只是进行读操作的话就没必要进行加锁
在封锁一节中提到加锁能解决多个事务同时执行时出现的并发一致性问题在实际场景中读操作往往多于写操作因此又引入了读写锁来避免不必要的加锁操作例如读和读没有互斥关系读写锁中读和写操作仍然是互斥的 MVCC 利用了多版本的思想写操作更新最新的版本快照而读操作去读旧版本快照没有互斥关系这一点和 CopyOnWrite 类似

MVCC 的读操作不需要进行加锁并且在可重复读隔离级别下能解决脏读和不可重复读问题它的基本思想是为每个数据行维护多个版本的快照多个事务可以同时去操作这个数据行
MVCC 中事务的修改操作DELETEINSERTUPDATE会为数据行新增一个版本快照

事务的修改操作DELETEINSERTUPDATE会去修改该事务对应版本的快照
脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改在事务进行读取操作时为了解决脏读和不可重复读问题MVCC 规定只能读取已经提交的快照当然一个事务可以读取自身未提交的快照这不算是脏读

## 版本号

脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改在事务在进行读取操作时为了解决脏读和不可重复读问题读取的快照需要满足以下条件快照在该事务开始之后没有被其它事务修改否则会读取到其它事务的未提交的修改操作
- 系统版本号 SYS_ID是一个递增的数字每开始一个新的事务系统版本号就会自动递增
- 事务版本号 TRX_ID事务开始时的系统版本号

## Undo 日志

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

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

```sql
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 机制每个操作都会被当成一个事务来执行所以上面总共涉及到三个事务

<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208012527591.png"/> </div><br>

## 版本号

- 系统版本号是一个递增的数字每开始一个新的事务系统版本号就会自动递增
- 事务版本号事务开始时的系统版本号

## 隐藏的列

MVCC 在每个数据行后面都保存着两个隐藏的列用来存储两个版本号

- 创建版本号指示创建一个数据行的快照时的系统版本号
- 删除版本号如果该快照的删除版本号未定义或删除版本号大于当前事务版本号表示该快照有效

## 实现过程

以下实现过程针对可重复读隔离级别

因为数据行快照的创建版本号是创建数据行快照时的系统版本号系统版本号随着创建事务而递增所以新开始一个事务时这个事务的系统版本号比之前的系统版本号都大也就是比当前所有数据行快照的创建版本号都大
因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行根据 MySQL AUTOCOMMIT 机制每个操作都会被当成一个事务来执行所以上面的操作总共涉及到三个事务快照中除了记录事务版本号 TRX_ID 和操作之外还记录了一个 bit DEL 字段用于标记是否被删除

### 1. SELECT
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208164808217.png"/> </div><br>

多个事务必须读取到同一个数据行的快照并且这个快照是距离现在最近的一个有效快照但是也有例外如果有一个事务正在修改该数据行那么它可以读取事务本身所做的修改而不用和其它事务的读取结果一致
INSERTUPDATEDELETE 操作会创建一个日志并将事务版本号 TRX_ID 写入DELETE 可以看成是一个特殊的 UPDATE还会额外将 DEL 字段设置为 1

把没有对一个数据行做修改的事务称为 TT 所要读取的数据行快照的创建版本号必须小于等于 T 的版本号因为如果大于 T 的版本号那么表示该数据行快照是其它事务的最新修改因此不能去读取它除此之外T 所要读取的数据行快照的删除版本号必须是未定义或者大于 T 的版本号因为如果小于等于 T 的版本号那么表示该数据行快照是已经被删除的不应该去读取它
## ReadView

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

将当前系统版本号作为数据行快照的创建版本号
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208171445674.png"/> </div><br>

### 3. DELETE
在进行 SELECT 操作时根据数据行快照的 TRX_ID TRX_ID_MIN TRX_ID_MAX 之间的关系从而判断数据行快照是否可以使用

将当前系统版本号作为数据行快照的删除版本号
- TRX_ID < TRX_ID_MIN表示该数据行快照时在当前所有未提交事务之前进行更改的因此可以使用

### 4. UPDATE
- TRX_ID > TRX_ID_MAX表示该数据行快照是在事务启动之后被更改的因此不可使用
- TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX需要根据隔离级别再进行判断
- 提交读如果 TRX_ID TRX_IDs 列表中表示该数据行快照对应的事务还未提交则该快照不可使用否则表示已经提交可以使用
- 可重复读都不可以使用因为如果可以使用的话那么其它事务也可以读到这个数据行快照并进行修改那么当前事务再去读这个数据行得到的值就会发生改变也就是出现了不可重复读问题

将当前系统版本号作为更新前的数据行快照的删除版本号并将当前系统版本号作为更新后的数据行快照的创建版本号可以理解为先执行 DELETE 后执行 INSERT
在数据行快照不可使用的情况下需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照再进行上面的判断

## 快照读与当前读

Expand All @@ -329,7 +314,7 @@ SELECT * FROM table ...;

### 2. 当前读

MVCC 其它会对数据库进行修改的操作INSERTUPDATEDELETE需要进行加锁操作从而读取最新的数据
MVCC 其它会对数据库进行修改的操作INSERTUPDATEDELETE需要进行加锁操作从而读取最新的数据可以看到 MVCC 并不是完全不用加锁而只是避免了 SELECT 的加锁操作

```sql
INSERT;
Expand Down

0 comments on commit 827725d

Please sign in to comment.