mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-20 22:17:09 +08:00
添加 数据库基础知识
This commit is contained in:
parent
9811751891
commit
c1705c32d2
19
README.md
19
README.md
@ -137,18 +137,19 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle
|
||||
|
||||
**总结:**
|
||||
|
||||
1. **[MySQL知识点总结](docs/database/MySQL.md)** (必看 :+1:)
|
||||
2. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md)
|
||||
3. [一千行 MySQL 学习笔记](docs/database/一千行MySQL命令.md)
|
||||
4. [MySQL 高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md)
|
||||
1. [数据库基础知识总结](docs/database/数据库基础知识.md)
|
||||
2. **[MySQL知识点总结](docs/database/mysql/MySQL总结.md)** (必看 :+1:)
|
||||
3. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/mysql/阿里巴巴开发手册数据库部分的一些最佳实践.md)
|
||||
4. [一千行 MySQL 学习笔记](docs/database/mysql/一千行MySQL学习笔记.md)
|
||||
5. [MySQL 高性能优化规范建议](docs/database/mysql/MySQL高性能优化规范建议.md)
|
||||
|
||||
**重要知识点:**
|
||||
|
||||
1. [MySQL数据库索引总结](docs/database/MySQL数据库索引.md)
|
||||
2. [事务隔离级别(图文详解)](<docs/database/事务隔离级别(图文详解).md>)
|
||||
3. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md)
|
||||
4. [关于数据库中如何存储时间的一点思考](docs/database/关于数据库存储时间的一点思考.md)
|
||||
5. [InnoDB存储引擎对MVCC的实现](docs/database/InnoDB对MVCC的实现.md)
|
||||
1. [MySQL数据库索引总结](docs/database/mysql/MySQL数据库索引.md)
|
||||
2. [事务隔离级别(图文详解)](docs/database/mysql/事务隔离级别(图文详解).md)
|
||||
3. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/mysql/一条sql语句在mysql中如何执行的.md)
|
||||
4. [关于数据库中如何存储时间的一点思考](docs/database/mysql/关于数据库存储时间的一点思考.md)
|
||||
5. [InnoDB存储引擎对MVCC的实现](docs/database/mysql/InnoDB对MVCC的实现.md)
|
||||
|
||||
### Redis
|
||||
|
||||
|
@ -1,34 +1,30 @@
|
||||
<!-- TOC -->
|
||||
|
||||
- [一致性非锁定读和锁定读](#一致性非锁定读和锁定读)
|
||||
- [一致性非锁定读](#一致性非锁定读)
|
||||
- [锁定读](#锁定读)
|
||||
- [InnoDB对MVCC的实现](#InnoDB对MVCC的实现)
|
||||
- [隐藏字段](#隐藏字段])
|
||||
- [ReadView](#ReadView)
|
||||
- [undo-log](#undo-log)
|
||||
- [数据可见性算法](#数据可见性算法)
|
||||
- [RC、RR隔离级别下MVCC的差异](#RC、RR隔离级别下MVCC的差异)
|
||||
- [MVCC解决不可重复读问题](#MVCC解决不可重复读问题)
|
||||
- [在RC下ReadView生成情况](#在RC下ReadView生成情况)
|
||||
- [在RR下ReadView生成情况](#在RR下ReadView生成情况)
|
||||
- [MVCC+Next-key-Lock防止幻读](#MVCC➕Next-key-Lock防止幻读)
|
||||
- [一致性非锁定读](#一致性非锁定读)
|
||||
- [锁定读](#锁定读)
|
||||
- [InnoDB 对 MVCC 的实现](#InnoDB对MVCC的实现)
|
||||
- [隐藏字段](#隐藏字段])
|
||||
- [ReadView](#ReadView)
|
||||
- [undo-log](#undo-log)
|
||||
- [数据可见性算法](#数据可见性算法)
|
||||
- [RC、RR 隔离级别下 MVCC 的差异](#RC、RR隔离级别下MVCC的差异)
|
||||
- [MVCC 解决不可重复读问题](#MVCC解决不可重复读问题)
|
||||
- [在 RC 下 ReadView 生成情况](#在RC下ReadView生成情况)
|
||||
- [在 RR 下 ReadView 生成情况](#在RR下ReadView生成情况)
|
||||
- [MVCC+Next-key-Lock 防止幻读](#MVCC➕Next-key-Lock防止幻读)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
|
||||
|
||||
## 一致性非锁定读和锁定读
|
||||
|
||||
#### 一致性非锁定读
|
||||
|
||||
对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
|
||||
对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
|
||||
|
||||
在 `InnoDB` 存储引擎中,[多版本控制 (multi versioning)](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html) 就是对非锁定读的实现。如果读取的行正在执行 `DELETE` 或 `UPDATE` 操作,这时读取操作不会去等待行上锁的释放。相反地,`InnoDB` 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
|
||||
|
||||
在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,` select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读
|
||||
|
||||
|
||||
在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,`select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读
|
||||
|
||||
#### 锁定读
|
||||
|
||||
@ -38,35 +34,25 @@
|
||||
- select ... for update
|
||||
- insert、update、delete 操作
|
||||
|
||||
|
||||
|
||||
在锁定读下,读取的是数据的最新版本,这种读也被称为 `当前读(current read)`。锁定读会对读取到的记录加锁:
|
||||
|
||||
- `select ... lock in share mode`:对记录加 `S` 锁,其它事务也可以加`S`锁,如果加 `x` 锁则会被阻塞
|
||||
|
||||
- `select ... for update`、`insert`、`update`、`delete `:对记录加 `X` 锁,且其它事务不能加任何锁
|
||||
- `select ... for update`、`insert`、`update`、`delete`:对记录加 `X` 锁,且其它事务不能加任何锁
|
||||
|
||||
在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成),但如果是`当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。**所以 `InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
|
||||
|
||||
|
||||
在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据Read View判断数据可见性,Read View在第一次查询时生成),但如果是`当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。**所以 `InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
|
||||
|
||||
|
||||
|
||||
## InnoDB对MVCC的实现
|
||||
## InnoDB 对 MVCC 的实现
|
||||
|
||||
`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
|
||||
|
||||
|
||||
|
||||
#### 隐藏字段
|
||||
|
||||
在内部,`InnoDB` 存储引擎为每行数据添加了三个 [隐藏字段](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html):
|
||||
|
||||
- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除
|
||||
- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务 id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除
|
||||
- `DB_ROLL_PTR(7字节)` 回滚指针,指向该行的 `undo log` 。如果该行未被更新,则为空
|
||||
- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该id来生成聚簇索引
|
||||
|
||||
|
||||
- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该 id 来生成聚簇索引
|
||||
|
||||
#### ReadView
|
||||
|
||||
@ -74,12 +60,10 @@
|
||||
|
||||
主要有以下字段:
|
||||
|
||||
- `m_low_limit_id`:目前出现过的最大的事务ID+1,即下一个将被分配的事务ID。大于这个ID的数据版本均不可见
|
||||
- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个ID的数据版本均可见
|
||||
- `m_ids`:`Read View` 创建时其他未提交的活跃事务ID列表。创建 `Read View `时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中)
|
||||
- `m_creator_trx_id`:创建该 `Read View` 的事务ID
|
||||
|
||||
|
||||
- `m_low_limit_id`:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于这个 ID 的数据版本均不可见
|
||||
- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务 ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个 ID 的数据版本均可见
|
||||
- `m_ids`:`Read View` 创建时其他未提交的活跃事务 ID 列表。创建 `Read View`时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中)
|
||||
- `m_creator_trx_id`:创建该 `Read View` 的事务 ID
|
||||
|
||||
#### undo-log
|
||||
|
||||
@ -88,43 +72,29 @@
|
||||
- 当事务回滚时用于将数据恢复到修改前的样子
|
||||
- 另一个作用是 `MVCC` ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 `undo log` 读取之前的版本数据,以此实现非锁定读
|
||||
|
||||
|
||||
|
||||
**在 `InnoDB` 存储引擎中 `undo log` 分为两种: `insert undo log` 和 `update undo log`:**
|
||||
|
||||
|
||||
|
||||
1. **`insert undo log`** :指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作
|
||||
|
||||
|
||||
|
||||
**`insert` 时的数据初始状态:**
|
||||
|
||||

|
||||
|
||||
2. **`update undo log`** :`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
|
||||
|
||||
|
||||
|
||||
**数据第一次被修改时:**
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
**数据第二次被修改时:**
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录
|
||||
|
||||
|
||||
|
||||
#### 数据可见性算法
|
||||
|
||||
在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有commit)的事务的ID号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务ID列表(即m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务ID进行比较,判断是否满足可见性条件
|
||||
在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件
|
||||
|
||||
[具体的比较算法](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L161)如下:[图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1)
|
||||
|
||||
@ -132,30 +102,26 @@
|
||||
|
||||
1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
|
||||
|
||||
2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤5
|
||||
2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5
|
||||
|
||||
3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的
|
||||
|
||||
4. 如果 m_up_limit_id <= DB_TRX_ID < m_up_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)
|
||||
|
||||
- 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:①在当前事务创建快照前,该记录行的值被事务ID为 DB_TRX_ID 的事务修改了,但没有提交;或者 ②在当前事务创建快照后,该记录行的值被事务ID为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤5
|
||||
- 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
|
||||
|
||||
- 在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
|
||||
- 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
|
||||
|
||||
5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤1重新开始判断,直到找到满足的快照版本或返回空
|
||||
5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空
|
||||
|
||||
## RC 和 RR 隔离级别下 MVCC 的差异
|
||||
|
||||
在事务隔离级别 `RC` 和 `RR` (InnoDB 存储引擎的默认事务隔离级别)下,`InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同
|
||||
|
||||
## RC和RR隔离级别下MVCC的差异
|
||||
- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids 列表)
|
||||
- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids 列表)
|
||||
|
||||
在事务隔离级别 `RC` 和 `RR` (InnoDB存储引擎的默认事务隔离级别)下,` InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同
|
||||
|
||||
- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids列表)
|
||||
- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids列表)
|
||||
|
||||
|
||||
|
||||
## MVCC解决不可重复读问题
|
||||
## MVCC 解决不可重复读问题
|
||||
|
||||
虽然 RC 和 RR 都通过 `MVCC` 来读取快照数据,但由于 **生成 Read View 时机不同**,从而在 RR 级别下实现可重复读
|
||||
|
||||
@ -163,63 +129,51 @@
|
||||
|
||||

|
||||
|
||||
|
||||
#### **在RC下ReadView生成情况**
|
||||
#### **在 RC 下 ReadView 生成情况**
|
||||
|
||||
1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`:**
|
||||
|
||||

|
||||
|
||||
由于 RC 级别下每次查询都会生成` Read View` ,并且事务101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
|
||||
|
||||
- 此时最新记录的 `DB_TRX_ID` 为101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见
|
||||
- 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花`
|
||||
|
||||
由于 RC 级别下每次查询都会生成`Read View` ,并且事务 101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
|
||||
|
||||
- 此时最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
|
||||
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
|
||||
|
||||
2. **`时间线来到 T6 ,数据的版本链为`:**
|
||||
|
||||

|
||||
|
||||
因为在 RC 级别下,重新生成 `Read View`,这时事务101已经提交,102并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103
|
||||
|
||||
- 此时最新记录的 `DB_TRX_ID` 为102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为101,满足 102 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读!
|
||||
因为在 RC 级别下,重新生成 `Read View`,这时事务 101 已经提交,102 并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103
|
||||
|
||||
- 此时最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,满足 102 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读!
|
||||
|
||||
3. **`时间线来到 T9 ,数据的版本链为`:**
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务ID为102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六`
|
||||
重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六`
|
||||
|
||||
> **总结:** **在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读**
|
||||
|
||||
#### **在 RR 下 ReadView 生成情况**
|
||||
|
||||
> **总结:** **在RC隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读**
|
||||
|
||||
|
||||
#### **在RR下ReadView生成情况**
|
||||
|
||||
**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个Read View(m_ids列表)**
|
||||
**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)**
|
||||
|
||||
1. **`在 T4 情况下的版本链为`:**
|
||||
|
||||
|
||||

|
||||
|
||||
在当前执行 `select` 语句时生成一个 `Read View`,此时 **`m_ids`:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
|
||||
|
||||
此时和 RC 级别下一样:
|
||||
|
||||
- 最新记录的 `DB_TRX_ID` 为101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见
|
||||
- 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花`
|
||||
|
||||
- 最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
|
||||
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
|
||||
|
||||
2. **`时间点 T6 情况下`:**
|
||||
|
||||
@ -227,28 +181,21 @@
|
||||
|
||||
在 RR 级别下只会生成一次`Read View`,所以此时依然沿用 **`m_ids` :[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
|
||||
|
||||
- 最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
|
||||
- 最新记录的 `DB_TRX_ID` 为102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
|
||||
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为101,不可见
|
||||
|
||||
- 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见
|
||||
|
||||
- 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花`
|
||||
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,不可见
|
||||
|
||||
- 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
|
||||
|
||||
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
|
||||
|
||||
3. **时间点 T9 情况下:**
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids` :[101,102]** ,所以查询结果依然是 `name = 菜花`
|
||||
|
||||
|
||||
|
||||
## MVCC➕Next-key-Lock防止幻读
|
||||
## MVCC➕Next-key-Lock 防止幻读
|
||||
|
||||
`InnoDB`存储引擎在 RR 级别下通过 `MVCC`和 `Next-key Lock` 来解决幻读问题:
|
||||
|
||||
@ -256,15 +203,13 @@
|
||||
|
||||
在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 `Read View` ,并使用至事务提交。所以在生成 `Read View` 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”
|
||||
|
||||
2. **执行select...for update/lock in share mode、insert、update、delete等当前读**
|
||||
2. **执行 select...for update/lock in share mode、insert、update、delete 等当前读**
|
||||
|
||||
在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!`InnoDB` 使用 [Next-key Lock](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-next-key-locks) 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读
|
||||
|
||||
|
||||
|
||||
## 参考
|
||||
|
||||
- **《MySQL技术内幕InnoDB存储引擎第2版》**
|
||||
- **《MySQL 技术内幕 InnoDB 存储引擎第 2 版》**
|
||||
|
||||
- [Innodb中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)
|
||||
- [MySQL事务与MVCC如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141)
|
||||
- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)
|
||||
- [MySQL 事务与 MVCC 如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141)
|
@ -22,32 +22,12 @@ MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录
|
||||
|
||||
由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是**3306**。
|
||||
|
||||
### 关系型数据库的设计范式?
|
||||
|
||||
**范式**是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”,可以粗略的理解为**一张数据表的表结构所符合的某种设计标准的级别**。
|
||||
|
||||
- **第一范式**(1NF):符合1NF的关系中的每个属性不可再分。另外第一范式是所有关系型数据库的最基本要求。
|
||||
|
||||
第一范式的存在的问题:数据冗余过大、插入异常、删除异常、修改异常等问题。
|
||||
|
||||
- **第二范式(2NF)**:在符合1NF的基础上,消除了非主属性对于码的部分函数依赖。
|
||||
|
||||
第二范式存在的问题: 有可能会出现非主属性对码的传递依赖。
|
||||
|
||||
- **第三范式(3NF):** 在符合2NF的基础上,消除了非主属性对码的传递函数依赖。也就是说,如果存在非主属性对于码的传递函数依赖,则不符合第三范式的要求。
|
||||
|
||||
第三范式存在的问题:存在着**主属性**对于码的部分函数依赖与传递函数依赖。
|
||||
|
||||
- **BC范式(BCNF)**: 在符合3NF 的基础上消除了主属性对码的部分函数依赖和传递函数依赖。
|
||||
|
||||
摘自这篇文章[《如何理解关系型数据库的常见设计范式?》](https://www.zhihu.com/question/24696366/answer/29189700)
|
||||
|
||||
## 存储引擎
|
||||
|
||||
### 存储引擎相关的命令
|
||||
|
||||
**查看MySQL提供的所有存储引擎**
|
||||
=======
|
||||
**查看 MySQL 提供的所有存储引擎**
|
||||
|
||||
```sql
|
118
docs/database/数据库基础知识.md
Normal file
118
docs/database/数据库基础知识.md
Normal file
@ -0,0 +1,118 @@
|
||||
数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS:这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。
|
||||
|
||||
## 什么是数据库,数据库管理系统,数据库系统,数据库管理员?
|
||||
|
||||
- **数据库** :数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
|
||||
- **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用语用于建立、使用和维护数据库。
|
||||
- **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
|
||||
- **数据库管理员** : 数据库管理员(Database Administrator,简称 DBA)负责全面管理和控制数据库系统。
|
||||
|
||||
数据库系统基本构成如下图所示:
|
||||
|
||||

|
||||
|
||||
## 什么是元组,码,候选码,主码,外码,主属性,非主属性?
|
||||
|
||||
- **元组** : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
|
||||
- **码** :码就是能唯一标识实体的属性,对应表中的列。
|
||||
- **候选码** : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
|
||||
- **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
|
||||
- **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
|
||||
- **主属性** : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门).显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
|
||||
- **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
|
||||
|
||||
## 主键和外键有什么区别?
|
||||
|
||||
- **主键(主码)** :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
|
||||
- **外键(外码)** :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
|
||||
|
||||
## 什么是 ER 图?
|
||||
|
||||
> 我们做一个项目的时候一定要试着花 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问道的。
|
||||
|
||||
**E-R 图**也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
|
||||
|
||||
下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M:N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1:N)。
|
||||
|
||||

|
||||
|
||||
我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理):
|
||||
|
||||

|
||||
|
||||
## 数据库范式了解吗?
|
||||
|
||||
**1NF(第一范式)**
|
||||
|
||||
属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。
|
||||
|
||||
**2NF(第二范式)**
|
||||
|
||||
2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
|
||||
|
||||

|
||||
|
||||
一些重要的概念:
|
||||
|
||||
- **函数依赖(functional dependency)** :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
|
||||
- **部分函数依赖(partial functional dependency)** :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号);
|
||||
- **完全函数依赖(Full functional dependency)** :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
|
||||
- **传递函数依赖** : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。
|
||||
|
||||
**3NF(第三范式)**
|
||||
|
||||
3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
|
||||
|
||||
**总结**
|
||||
|
||||
- 1NF:属性不可再分。
|
||||
- 2NF:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
|
||||
- 3NF:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
|
||||
|
||||
## 什么是存储过程?
|
||||
|
||||
我们可以把存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。
|
||||
|
||||
存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。
|
||||
|
||||
阿里巴巴 Java 开发手册里要求禁止使用存储过程。
|
||||
|
||||

|
||||
|
||||
## drop、delete 与 truncate 区别?
|
||||
|
||||
### 用法不同
|
||||
|
||||
- drop(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。
|
||||
- truncate (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
|
||||
- delete(删除数据) : `delete from 表名 where 列名=值`,删除某一列的数据,如果不加 where 子句和`truncate table 表名`作用类似。
|
||||
|
||||
truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 **truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。**
|
||||
|
||||
### 属于不同的数据库语言
|
||||
|
||||
truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。
|
||||
|
||||
**DML 语句和 DDL 语句区别:**
|
||||
|
||||
- DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。
|
||||
- DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
|
||||
|
||||
### 执行速度不同
|
||||
|
||||
一般来说:drop>truncate>delete(这个我没有设计测试过)。
|
||||
|
||||
## 数据库设计通常分为哪几步?
|
||||
|
||||
1. **需求分析** : 分析用户的需求,包括数据、功能和性能需求。
|
||||
2. **概念结构设计** : 主要采用 E-R 模型进行设计,包括画 E-R 图。
|
||||
3. **逻辑结构设计** : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
|
||||
4. **物理结构设计** : 主要是为所设计的数据库选择合适的存储结构和存取路径。
|
||||
5. **数据库实施** : 包括编程、测试和试运行
|
||||
6. **数据库的运行和维护** : 系统的运行与数据库的日常维护。
|
||||
|
||||
## 参考
|
||||
|
||||
- <https://blog.csdn.net/rl529014/article/details/48391465>
|
||||
- <https://www.zhihu.com/question/24696366/answer/29189700>
|
||||
- <https://blog.csdn.net/bieleyang/article/details/77149954>
|
Loading…
x
Reference in New Issue
Block a user