数据库复习——基础知识

This commit is contained in:
Estom
2021-09-01 22:20:48 +08:00
parent 1bd1270276
commit 2fb934ec92
49 changed files with 1344 additions and 379 deletions

View File

45
TASK.md
View File

@@ -1,45 +0,0 @@
# 任务清单
> * 感觉这玩意有点像无主之地的任务列表
> * 需要提高自己的执行效率,像话这么多时间搞计网实验,就全乱了。
## 待完成任务
- [ ] 编程__2019.10.07__Linux系统管理(需要一本书,以及买一本命令参考手册)
- [ ] 编程__2019.10.07__群体智能算法实现(需要一本书,以及一个系统的教程)
- [ ] 编程__2019.10.15__遗传算法实现
- [ ] 编程__2019.10.25__神经网络算法学习与实现。完成具体任务。
- [ ] 编程__2019.10.01__C++拓展深入学习(需要一本书,以及相关的教程)
- [ ] 编程__2019.10.07__C++与Python数据可视化工具
- [ ] 编程__2019.11.10__数据结构复习整理笔记+C++代码实现。
- [ ] 编程__2019.10.10__基础算法复习+C++实现
- [ ] 编程__2019.11.20__编译原理复习+重新整理笔记
- [ ] 编程__2019.11.30__程序设计语言原理学习+笔记整理
- [ ] 文献__2019.10.30__Latex模板整理基础内容可能不需要整理因为只要有一份参考文档可以随时查阅就行了但是需要整理自己能够使用的latex论文模板
- [ ] 英语__2019.10.20__考研英语词汇
- [ ] 数学__2019.09.25__微积分
- [ ] 数学__2019.09.30__复变函数(卷积与函数变换)
- [ ] 数学__2019.10.10__线性代数复习
- [ ] 学习__2019.10.20__PPT
- [ ] 学习__2019.10.25__Excel
- [ ] 学习__2019.10.30__Word
- [ ] 学习__2020.02.24__大一笔记整理
- [ ] 学习__2020.02.24__大二笔记整理
- [ ] 学习__2020.02.24__大三笔记整理
- [ ] 学习__2020.02.24__大四笔记整理
- [ ] 读书__2020.02.24__哲学经济六本
- [ ] 读书__2020.02.24__国外文学六本
- [ ] 读书__2020.02.24__哈利波特六本
## 已完成任务
- [x] 学习__2019.09.20__概率基础知识补充
- [x] 读书__2019.09.20__弃业医生日志

View File

@@ -0,0 +1,8 @@
#include<iostream>
using namespace std;
int main(){
string s("123");
cout<<s<<endl;
}

41
markdown.md Normal file
View File

@@ -0,0 +1,41 @@
## markdonw绘图
### 时序图
```sequence
A->B:因果关系
B->A:返回值
```
### 流程图
```flow
st=>start: 第一步
op=>operation: 第二步
cond=>condition: Yes or No?
e=>end
st->op->cond
cond(yes)->e
cond(no)->op
```
### 右向图
```mermaid
graph LR
A[第一步<br/>第二行] -->B(第二步<br/>第二行)
B --> C{决策}
C -->|原因1| D[结果1<br/>第二行]
C -->|原因2| E[结果2<br/>第二行]
```
### 扇形图
```mermaid
pie
title Pie Chart
"原因一" : 99
"原因二" : 666
"原因三" : 188
```

View File

@@ -14,8 +14,9 @@
* [ ] 并发
* [ ] 数据库
* [ ] 基础知识
* [ ] MySQL
* [ ] Redis
* [ ] MySQL:分库分表、主从库、主从库同步方法。
* [ ] Redis:数据库击穿
* [ ] 消息队列相关内容
* [ ] 计算机网络
* [ ] 应用层
* [ ] 网络层

View File

@@ -0,0 +1,53 @@
## 计划
> 以后每天电脑上进度和手机日历上的进度进行同步。
### 周一
* [x] 拆快递
* [x] 买剃须刀
* [x] 百度一面
* [ ] 视频分享查看。这两次的。
* [x] 扫黑风暴(开始)
* [x] 脱口秀大会(每天晚上)
* [x] 制定这一周的计划。
### 周二
* [ ] 数据库
* [ ] 操作系统
* [ ] 计算机网络
* [ ] 美团一面
* [ ] 第二波简历投递。包括银行、国企和外企。
* [ ] 完成括号内外分享。(中西文)
* [ ] 开心麻花or德云社。乌龙山伯爵、瞎话艺术家、牢友记
### 周三
* [ ] 数据结构
* [ ] 小米笔试
### 周四
* [ ] 算法
### 周五
* [ ] 面试项目经历准备。
* [ ] 按摩店体验。
### 周六
* [ ] 括号内外分享会
### 周日
* [ ] 开心麻花?德云社剧场体验

View File

@@ -2,11 +2,7 @@
# 九月份计划
## 简历投递
-------------------------------------------------
互联网企业
## 互联网企业
### 字节
@@ -29,8 +25,9 @@
* 7、对机器学习系统Tensorflow/Caffe/MXNet和算法CNN/RNN/LSTM/VAE有理解、认识或经验者优先
* 8、对隐私计算联邦学习、多方安全计算、机密计算有了解优先。
* 流程
* [ ] 简历投递与直通终面协商。https://jobs.bytedance.com/campus/position/application
* [x] 简历投递与直通终面协商。https://jobs.bytedance.com/campus/position/application
* [ ] 直通终面时间2021-09-13 15:30面试链接https://people.toutiaocloud.com/hire/bridge/video/interviewee/fe3ffa33-19ab-4be9-aa9e-4bd1af49d05c
### 阿里
* 岗位
@@ -47,13 +44,17 @@
* 或许,你在学校的时候作为骨干参与学生网站的建设和开发;
* 但是,这些都是我们想要的。
* 流程
* [x] 直通终面(但愿是吧看到已经投了。就坐等吧。)https://talent.alibaba.com/personal/campus-application
* [x] 简历投递 https://talent.alibaba.com/personal/campus-application
* [ ] 直通终面(听寒少说相关内容还在操作和安排。)
### 腾讯
* 流程
* [ ] fuck 居然杯智能车捞起来了,着实,不太行。
### 百度
* 岗位:
@@ -70,6 +71,9 @@
* -具备专业领域的计算机知识和技能:搜索引擎、数据挖掘/机器学习、多媒体、地理信息系统、云计算、分布式系统等
* 流程
* [x] 建立投递https://talent.baidu.com/external/baidu/campus.html#/individualCenter
* [x] 百度一面2021-08-30 14:00-15:00https://code.meideng.net/ykl1
### 360
@@ -85,6 +89,7 @@
* 流程
* [x] 官网投递https://app.mokahr.com/campus_apply/360campus/26419#/candidateHome/applications
* [x] 360笔试021-08-29 15:00 星期天
### 美团
@@ -102,6 +107,9 @@
3. 有在知名公司核心部门的实习经历且实习时间不少于6个月。
* 流程
* [x] 官网投递https://campus.meituan.com/apply-record
* [x] 美团笔试2021-08-29 10:00 星期天
* [x] 美团一面北京时间08月31日 19:00面试类型视频面试面试地址http://dpurl.cn/0wnxIGBz
* [ ] 美团二面北京时间09月02日 14:00http://dpurl.cn/4knwRG4z
### 网易
* 岗位:
@@ -123,8 +131,10 @@
### 携程
---------------------------------------------------------
电商
----
## 电商
### 京东
@@ -133,8 +143,10 @@
### 唯品会
### 拼多多
--------------------------------------------------------
手机厂商
----
## 手机厂商
### 华为
@@ -159,7 +171,7 @@
* 2有IT应用软件、互联网软件、IOS/安卓等相关产品开发经验,不满足于课堂所学,在校期间积极参加校内外软件编程大赛或积极参于编程开源社区组织;
* 3熟悉JS/AS/AJAX/HTML5/CSS等前端开发技术。
* 流程
* [ ] 简历投递https://career.huawei.com/reccampportal/portal5/campus-recruitment-detail.html?jobId=176044
* [x] 简历投递https://career.huawei.com/reccampportal/portal5/campus-recruitment-detail.html?jobId=176044
### 小米
@@ -176,7 +188,8 @@
* 3、对数据结构算法比较了解。
* 进度
* [x] 建立投递。官网烂的一批。https://app.mokahr.com/campus_apply/xiaomi/286#/job/dc8d7233-d059-4df9-938d-c0a01be881f7
* [ ] 要做测评(邮箱里有测评的链接。)
* [x] 要做测评(邮箱里有测评的链接。)\
* [ ] 在线笔试2021-09-01 19:00 星期三https://mi.acmcoder.com/cand/login?673rqtpa
### vivo
@@ -199,8 +212,9 @@
--------------------------------------------------------
国企研究所
----
## 国企研究所
@@ -223,8 +237,10 @@ http://qingdao.offcn.com/html/gqzp/
http://qingdao.offcn.com/html/2021/08/79827.html
### 中电14所
------
外企
----
## 外企
### 微软

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -9,7 +9,7 @@
### 4. SQL
### 5. 完整性约
### 5. 完整性约
### 6. 安全性

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,88 @@
# Mysql 架构原理
> 参考文献
> * [Mysql 架构原理](https://blog.csdn.net/hguisu/article/details/7106342)
## 1 概述
### 分层结构概述
<!-- ![](image/2021-09-01-22-02-49.png) -->
![](image/2021-09-01-22-05-53.png)
1. 客户端各种语言都提供了连接mysql数据库的方法比如jdbc、php、go等可根据选择 的后端开发语言选择相应的方法或框架连接mysql
2. server层包括连接器、查询缓存、分析器、优化器、执行器等涵盖mysql的大多数核心服务功能以及所有的内置函数例如日期、世家、数 学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
3. 存储引擎层:负责数据的存储和提取,是真正与底层物理文件打交道的组件。 数据本质是存储在磁盘上的通过特定的存储引擎对数据进行有组织的存放并根据业务需要对数据进行提取。存储引擎的架构模式是插件式的支持InnodbMyIASM、Memory等多个存储引擎。现在最常用的存储引擎是Innodb它从mysql5.5.5版本开始成为了默认存储引擎。
4. 物理文件层存储数据库真正的表数据、日志等。物理文件包括redolog、undolog、binlog、errorlog、querylog、slowlog、data、index等
### 连接器
连接器负责来自客户端的连接、获取用户权限、维持和管理连接。
一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建连接才会使用新的权限设置。
### 查询缓存
mysql拿到一个查询请求后会先到查询缓存查看之前是否执行过这条语句。前执行过的语句及其结果可能会以key-value对的形式被直接缓存在内存中。key是查询的语句value是查询的结果。如果当前sql查询能够直接在查询缓存中找到key那么这个value就会被直接返回给客户端。
### 分析器
词法分析(识别关键字,操作,表名,列名)
语法分析 (判断是否符合语法)
### 优化器
优化器是在表里面有多个索引的时候决定使用哪个索引或者在一个语句有多表关联join的时候决定各个表的连接顺序。优化器阶段完成后这个语句的执行方案就确定下来了然后进入执行器阶段。
### 执行器
开始执行的时候,要先判断一下用户对这个表 T 有没有执行查询的权限。如果没有,就会返回没有权限的错误。如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限。如果有权限就打开表继续执行。打开表的时候执行器就会根据表的引擎定义去调用这个引擎提供的接口。在数据库的慢查询日志中看到一个rows_examined 的字段表示这个语句执行过程中扫描了多少行这个值就是在执行器每次调用引擎获取数据航的时候累加的。在有些场景下执行器调用一次在引擎内部则扫描了多行因此引擎扫描行数跟rows_examined并不是完全相同的。
## 2 SQL查询机制
![](image/2021-09-01-22-06-49.png)
### 执行过程
SQL执行步骤请求、缓存、SQL解析、优化SQL查询、调用引擎执行返回结果:
1. 连接:客户端向 MySQL 服务器发送一条查询请求与connectors交互连接池认证相关处理。
2. 缓存:服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果,否则进入下一阶段
3. 解析服务器进行SQL解析(词法语法)、预处理。
4. 优化:再由优化器生成对应的执行计划。
5. 执行MySQL 根据执行计划,调用存储引擎的 API来执行查询。
6. 结果:将结果返回给客户端,同时缓存查询结果。
## 3 SQL更新机制和日志记录
![](image/2021-09-01-22-14-07.png)
### 执行过程
1. 客户端向 MySQL 服务器发送一条更新请求
2. 清除表查询缓存,跟这个有关的查询缓存会失效。这就是一般不建议使用查询缓存的原因。
3. 分析器进行 SQL解析词法和语法分析分析这是一条更新语句和。
4. 优化器生成对应的执行计划优化器决定使用ID这个索引
5. 执行器负责更新,找到这一行,然后进行更新:
1. 取数据行: 执行器先找引擎取 ID=2 这一行: ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。)
2. 更新数据: 执行器拿到引擎给的行数据,把这个值加上 1比如原来是 N现在就是 N+1得到新的一行数据再调用引擎接口写入这行新数据。
3. 更新内存: 引擎将这行新数据更新到内存中,
4. 更新 redo log :同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
5. 写入binlog:执行器生成这个操作的 binlog并把 binlog 写入磁盘。
6. 提交事务: 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交commit状态更新完成。
![](image/2021-09-01-22-16-48.png)
### 日志类型
与查询流程不同的是更新流程涉及两个重要日志模块redo log重做日志和 binlog归档日志。redo log是InnoDB存储引擎层的日志binlog是MySQL Server层记录的日志 两者都是记录了某些操作的日志(不是所有)自然有些重复(但两者记录的格式不同)。
![](image/2021-09-01-22-17-34.png)
redo log在数据库重启恢复的时候被使用因为其属于物理日志的特性恢复速度远快于逻辑日志。而binlog和undo log属于的逻辑日志。

View File

@@ -1,6 +1,9 @@
# MySQL存储引擎
## 1 概述
### MySQL架构
### 存储引擎
1. MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。

View File

@@ -1,3 +1,216 @@
# Mysql索引
> 参考文献
> * [MySQL索引类型](cnblogs.com/luyucheng/p/6289714.html)
## 1 索引类型——应用
### 索引定义
* 普通索引
* 唯一索引
* 主键索引
* 组合索引
* 全文索引
```
CREATE TABLE table_name[col_name data type]
[unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
```
1. unique|fulltext为可选参数分别表示唯一索引、全文索引
2. index和key为同义词两者作用相同用来指定创建索引
3. col_name为需要创建索引的字段列该列必须从数据表中该定义的多个列中选择
4. index_name指定索引的名称为可选参数如果不指定默认col_name为索引值
5. length为可选参数表示索引的长度只有字符串类型的字段才能指定索引长度
6. asc或desc指定升序或降序的索引值存储
### 普通索引
1.普通索引
是最基本的索引,它没有任何限制。它有以下几种创建方式:
1直接创建索引
```sql
CREATE INDEX index_name ON table(column(length))
```
2修改表结构的方式添加索引
```sql
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
```
3创建表的时候同时创建索引
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
INDEX index_name (title(length))
)
```
4删除索引
```sql
DROP INDEX index_name ON table
```
### 唯一索引
与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
1创建唯一索引
```sql
CREATE UNIQUE INDEX indexName ON table(column(length))
```
2修改表结构
```sql
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
```
3创建表的时候直接指定
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
UNIQUE indexName (title(length))
);
```
### 主键索引
是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) NOT NULL ,
PRIMARY KEY (`id`)
);
```
### 组合索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
```
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
```
### 全文索引
* FULLTEXT即为全文索引目前只有MyISAM引擎支持。其可以在CREATE TABLE ALTER TABLE CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR TEXT 列上可以创建全文索引。
* 主要用来查找文本中的关键字而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同它更像是一个搜索引擎而不是简单的where语句的参数匹配。
* fulltext索引配合match against操作使用而不是一般的where语句加like。它可以在create tablealter table create index使用不过目前只有char、varchartext 列上可以创建全文索引。值得一提的是在数据量较大时候现将数据放入一个没有全局索引的表中然后再用CREATE index创建fulltext索引要比先为一张表建立fulltext然后再将数据写入的速度快很多。
1创建表的适合添加全文索引
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
);
```
2修改表结构添加全文索引
```
ALTER TABLE article ADD FULLTEXT index_content(content)
```
3直接创建索引
```
CREATE FULLTEXT INDEX index_content ON article(content)
```
### 覆盖索引
* 覆盖索引select的数据列只用从索引中就能够取得不必读取数据行换句话说查询列要被所建的索引覆盖。
* 这个时候,可以不用访问指定的数据。也就不需要回表查询具体的数据,通过索引即可得到想要的数据。
### 索引缺点
1. 虽然索引大大提高了查询速度同时却会降低更新表的速度如对表进行insert、update和delete。因为更新表时不仅要保存数据还要保存一下索引文件。
2. 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。
索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
### 注意事项
1. 索引不会包含有null值的列。只要列中包含有null值都将不会被包含在索引中复合索引中只要有一列含有null值那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。
2. 使用短索引。对串列进行索引如果可能应该指定一个前缀长度。例如如果有一个char(255)的列如果在前10个或20个字符内多数值是惟一的那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
3. 索引列排序。查询只使用一个索引因此如果where子句中已经使用了索引的话那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作尽量不要包含多个列的排序如果需要最好给这些列创建复合索引。
4. like语句操作。一般情况下不推荐使用like操作如果非使用不可如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
5. 不要在列上进行运算。这将导致索引失效而进行全表扫描,例如
```sql
SELECT * FROM table_name WHERE YEAR(column_name)<2017;
```
6. 不使用not in和<>操作
## 2 索引类型——聚集
* **聚集索引**(聚簇索引):数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。
* **非聚集索引**(非聚簇索引):该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。
* **回表**:非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为**回表**。
使用以下语句进行查询,不需要进行二次查询,直接就可以从非聚集索引的节点里面就可以获取到查询列的数据。
```
select id, username from t1 where username = '小明'
select username from t1 where username = '小明'
```
但是使用以下语句进行查询就需要二次的查询去获取原数据行的score
```
select username, score from t1 where username = '小明'
```
## 3 索引类型——算法
### 顺序索引
* 顺序文件上的索引:按指定属性升序或降序存储的关系。在属性上建立一个顺序索引文件,索引文件有属性值和响应的元组指针组成。
### B+树索引
* B+树上的索引B+树的叶节点为属性值和相应的元组指针。具有动态平衡的特点。
### hash索引
* 散列索引(哈希索引):建立若干个桶,将索引属性按照其散列函数值映射到相应的桶中,同种存放索引属性值和响应的元组指针。散列索引具有查找速度快的特点。
* HASH索引可以一次定位不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效对于范围查询、排序及组合索引仍然效率不高。这是MySQL里默认和最常用的索引类型。
### 位图索引
* 位图索引:用为向量记录索引属性中可能出现的值,每个为向量对应一个可能的值。
### 前缀索引
* tire索引/前缀索引:用来索引字符串。
> 特点:索引能够加快数据库查询,需要占用一定的存储空间。基本表更新后,索引也需要更新。用户不必显示地选择索引,关系数据库管理系统在执行查询时,会自动选择合适的索引作为存储路径。
MySQL中常用的索引结构有B+树索引和哈希索引两种。目前建表用的B+树索引就是BTREE索引。
在MySQL中MyISAM和InnoDB两种存储引擎都不支持哈希索引。只有HEAP/MEMORY引擎才能显示支持哈希索引。

View File

@@ -0,0 +1,561 @@
## 4 多级锁协议
### 封锁粒度
MySQL 各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。
1. 表级锁定table-level
表级别的锁定是 MySQL 各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
使用**表级锁定的主要是 MyISAMMEMORYCSV**等一些非事务性存储引擎。
2. 行级锁定row-level
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用**行级锁定的主要是 InnoDB 存储引擎**。
3. 页级锁定page-level
页级锁定是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
使用**页级锁定的主要是 BerkeleyDB 存储引擎**。
优缺点:
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用场景:
- 从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如 Web 应用而行级锁则更适合于有大量按索引条件并发更新少量不同数据同时又有并发查询的应用如一些在线事务处理OLTP系统。
注意事项:
- InnoDB 行锁是通过给索引上的索引项加锁来实现的只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁
- 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
### 封锁类型
可以说 InnoDB 的锁定模式实际上可以分为四种共享锁S排他锁X意向共享锁IS和意向排他锁IX。意向锁是 InnoDB 自动加的,不需用户干预。
- 读写锁
- 共享锁Shared简写为 S 锁,又称读锁。当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。
- 互斥锁Exclusive简写为 X 锁,又称写锁、独占锁。如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。
- 意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。
- 如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。
- 而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。
### 封锁规则
读写锁有如下规定
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
意向锁在原来的 X/S 锁之上引入了 IX/IS**IX/IS 都是表锁**,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。主要是为了防止遍历整个行,判断是否有行级锁。有以下两个规定:
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
![](image/2021-09-01-17-37-28.png)
- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;
- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T<sub>1</sub> 想要对数据行 R<sub>1</sub> 加 X 锁,事务 T<sub>2</sub> 想要对同一个表的数据行 R<sub>2</sub> 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)
### 封锁协议
**一级封锁协议**
- 事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
- 可以解决丢失修改的问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
![](image/2021-09-01-17-54-00.png)
**二级封锁协议**
- 在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
- 可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
![](image/2021-09-01-17-54-50.png)
**三级封锁协议**
- 在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
- 可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
![](image/2021-09-01-17-55-26.png)
**两段锁协议**
- 加锁和解锁分为两个阶段进行。
- 可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。串行执行的事务互不干扰,不会出现并发一致性问题。
![](image/2021-09-01-18-26-00.png)
事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
```html
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
```
但不是必要条件,例如以下操作不满足两段锁协议,但它还是可串行化调度。
```html
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
```
**隐式与显示锁定**
- MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
- InnoDB 也可以使用特定的语句进行显示锁定:
```sql
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
```
## 5 多版本并发控制 MVCC
### 原理
**数据库并发的三种场景**
* 读读之间不存在冲突,无须加锁。
* 写写之间的冲突需要严格隔离,必须加锁。有线程安全问题。
* 读写之间的冲突导致了脏读、不可重复读、幻影读等问题。有线程安全问题。
**MVCC带来的好处**
* 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
* 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
### 定义
**多版本并发控制**Multi-Version Concurrency Control, MVCC是 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
- MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。
- 未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。
- 可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
**当前读**:它读取的是记录的最新版本读取时还要保证其他并发事务不能修改当前记录会对读取的记录进行加锁。MVCC 会对数据库进行修改的操作INSERT、UPDATE、DELETE需要进行加锁操作从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的读加锁操作。
**快照读**不加锁的select操作就是快照读即不加锁的非阻塞读MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
**版本号**MVCC 用来实现版本控制的序号
- 系统版本号 SYS_ID是一个递增的数字每开始一个新的事务系统版本号就会自动递增。
- 事务版本号 TRX_ID :事务开始时的系统版本号。
**实现手段**
* 隐式字段
* Undo日志
* ReadView
### 隐式字段
InnoDB 的 MVCC,是通过在每行记录后面保存**两个隐藏的列**来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。
* 创建时间(事务版本)
* 删除时间(事务版本)
* 回滚指针(指向上一个事务版本号)
这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的 ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的 ID.
### Undo日志
Undolog主要分为两种
* insert undo log。代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
* update undo log。事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要在快照读时也需要所以不能随便删除只有在快速读或事务回滚不涉及该日志时对应的日志才会被purge线程统一清除
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
例如在 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 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
![](image/2021-09-01-20-40-19.png)
INSERT、UPDATE、DELETE 操作会创建一个日志,并将事务版本号 TRX_ID 写入。DELETE 可以看成是一个特殊的 UPDATE还会额外将 DEL 字段设置为 1。
### ReadView
在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照记录并维护系统当前活跃事务的ID
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候对该记录创建一个Read View读视图把它比作条件用来判断当前事务能够看到哪个版本的数据。
MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。
![](image/2021-09-01-20-40-30.png)
在进行 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需要根据隔离级别再进行判断
- 提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
- 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。
在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
**purge**
* 从前面的分析可以看出为了实现InnoDB的MVCC机制更新或者删除操作都只是设置一下老记录的deleted_bit并不真正将过时的记录删除。
* 为了节省磁盘空间InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作purge线程自己也维护了一个read view这个read view相当于系统中最老活跃事务的read view;如果某个记录的deleted_bit为true并且DB_TRX_ID相对于purge线程的read view可见那么这条记录一定是可以被安全清除的。
## 6 间隙锁 Next-Key Locks
Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。
MVCC 不能解决幻影读问题Next-Key Locks 就是为了解决这个问题而存在的。在可重复读REPEATABLE READ隔离级别下使用 MVCC + Next-Key Locks 可以解决幻读问题。
### Record Locks
锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。
### Gap Locks
锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
```sql
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
```
### Next-Key Locks
它是 Record Locks 和 Gap Locks 的结合不仅锁定一个记录上的索引也锁定索引之间的间隙。它锁定一个前开后闭区间例如一个索引包含以下值10, 11, 13, and 20那么就需要锁定以下区间
```sql
(-, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +)
```
## 7 MVCC实例
下面看一下在 REPEATABLE READ 隔离级别下,MVCC 具体是如何操作的。
### 实现方法——Insert(1)
InnoDB 为新插入的每一行保存当前系统版本号作为版本号.
**第一个事务ID为1**
```sql
start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
```
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Select(2)
InnoDB 会根据以下两个条件检查每行记录,只有满足一下两个条件,才能作为结果返回:
- InnoDB 只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
- 行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除.
**第二个事务,ID 为 2**
```
start transaction;
select * from yang; //(1)
select * from yang; //(2)
commit;
```
此时执行(1)中的内容,得到如下的表内容:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Insert(3)
- InnoDB 会为删除的每一行保存当前系统的版本号(事务的 ID)作为删除标识.
- 假设在执行这个事务 ID 为 2 的过程中,刚执行到(1),这时,有另一个事务 ID 为 3 往这个表里插入了一条数据;
**第三个事务 ID 为 3;**
```sql
start transaction;
insert into yang values(NULL,'tian');
commit;
```
这时表中的数据如下
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
</tbody>
</table>
然后接着执行事务 2 中的(2),由于 id=4 的数据的创建时间(事务 ID 为 3),执行当前事务的 ID 为 2,而 InnoDB 只会查找事务 ID 小于等于当前事务 ID 的数据行,所以 id=4 的数据行并不会在执行事务 2 中的(2)被检索出来,在事务 2 中的两条 select 语句检索出来的数据都只会下表:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——DELETE(4)
假设在执行这个事务 ID 为 2 的过程中,刚执行到(1),假设事务执行完事务 3 后,接着又执行了事务 4;
**第四个事务ID为4**
```sql
start transaction;
delete from yang where id=1;
commit;
```
此时数据表如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
</tbody>
</table>
接着执行事务 ID 为 2 的事务(2),根据 SELECT 检索条件可以知道,它会检索创建时间(创建事务的 ID)小于当前事务 ID 的行和删除时间(删除事务的 ID)大于当前事务的行,而 id=4 的行上面已经说过,而 id=1 的行由于删除时间(删除事务的 ID)大于当前事务的 ID,所以事务 2 的(2)select \* from yang 也会把 id=1 的数据检索出来.所以,事务 2 中的两条 select 语句检索出来的数据都如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Update(5)
InnoDB 执行 UPDATE实际上是新插入了一行记录并保存其创建时间为当前事务的 ID同时保存当前事务 ID 到要 UPDATE 的行的删除时间.
假设在执行完事务 2 的(1)后又执行,其它用户执行了事务 3,4,这时,又有一个用户对这张表执行了 UPDATE 操作:
**第 5 个事务ID为5**
```
start transaction;
update yang set name='Long' where id=2;
commit;
```
根据 update 的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务 ID,得到表如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>Long</td>
<td>5</td>
<td>undefined</td>
</tr>
</tbody>
</table>
继续执行事务 2 的(2),根据 select 语句的检索条件,得到下表:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
还是和事务 2 中(1)select 得到相同的结果.

View File

@@ -0,0 +1,226 @@
# JOIN连接查询
> 参考文献
> * [MySQL的JOIN用法](https://www.cnblogs.com/hcfinal/p/10231694.html)
![](../image/2021-09-01-15-38-13.png)
* 数据库实例
```sql
1 CREATE TABLE t_blog(
2 id INT PRIMARY KEY AUTO_INCREMENT,
3 title VARCHAR(50),
4 typeId INT
5 );
6 SELECT * FROM t_blog;
7 +----+-------+--------+
8 | id | title | typeId |
9 +----+-------+--------+
10 | 1 | aaa | 1 |
11 | 2 | bbb | 2 |
12 | 3 | ccc | 3 |
13 | 4 | ddd | 4 |
14 | 5 | eee | 4 |
15 | 6 | fff | 3 |
16 | 7 | ggg | 2 |
17 | 8 | hhh | NULL |
18 | 9 | iii | NULL |
19 | 10 | jjj | NULL |
20 +----+-------+--------+
21 -- 博客的类别
22 CREATE TABLE t_type(
23 id INT PRIMARY KEY AUTO_INCREMENT,
24 name VARCHAR(20)
25 );
26 SELECT * FROM t_type;
27 +----+------------+
28 | id | name |
29 +----+------------+
30 | 1 | C++ |
31 | 2 | C |
32 | 3 | Java |
33 | 4 | C# |
34 | 5 | Javascript |
35 +----+------------+
```
## 1. 笛卡尔积
要理解各种JOIN首先要理解笛卡尔积。笛卡尔积就是将A表的每一条记录与B表的每一条记录强行拼在一起。所以如果A表有n条记录B表有m条记录笛卡尔积产生的结果就会产生n*m条记录。
## 2. 内连接INNER JOIN。
内连接INNER JOIN是最常用的连接操作。从数学的角度讲就是求两个表的交集从笛卡尔积的角度讲就是从笛卡尔积中挑出ON子句条件成立的记录。有INNER JOINWHERE等值连接STRAIGHT_JOIN,JOIN(省略INNER)四种写法。
```sql
SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId=t_type.id;
SELECT * FROM t_blog,t_type WHERE t_blog.typeId=t_type.id;
SELECT * FROM t_blog STRAIGHT_JOIN t_type ON t_blog.typeId=t_type.id; --注意STRIGHT_JOIN有个下划线
SELECT * FROM t_blog JOIN t_type ON t_blog.typeId=t_type.id;
+----+-------+--------+----+------+
| id | title | typeId | id | name |
+----+-------+--------+----+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
+----+-------+--------+----+------+
```
## 3. 左连接LEFT JOIN
左连接LEFT JOIN的含义就是求两个表的交集外加左表剩下的数据。依旧从笛卡尔积的角度讲就是先从笛卡尔积中挑出ON子句条件成立的记录然后加上左表中剩余的记录见最后三条
```
SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id;
+----+-------+--------+------+------+
| id | title | typeId | id | name |
+----+-------+--------+------+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 8 | hhh | NULL | NULL | NULL |
| 9 | iii | NULL | NULL | NULL |
| 10 | jjj | NULL | NULL | NULL |
+----+-------+--------+------+------+
```
## 4. 右连接RIGHT JOIN
同理右连接RIGHT JOIN就是求两个表的交集外加右表剩下的数据。再次从笛卡尔积的角度描述右连接就是从笛卡尔积中挑出ON子句条件成立的记录然后加上右表中剩余的记录见最后一条
```
SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;
+------+-------+--------+----+------------+
| id | title | typeId | id | name |
+------+-------+--------+----+------------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 6 | fff | 3 | 3 | Java |
| 7 | ggg | 2 | 2 | C |
| NULL | NULL | NULL | 5 | Javascript |
+------+-------+--------+----+------------+
```
## 5. 外连接OUTER JOIN
外连接就是求两个集合的并集。从笛卡尔积的角度讲就是从笛卡尔积中挑出ON子句条件成立的记录然后加上左表中剩余的记录最后加上右表中剩余的记录。另外MySQL不支持OUTER JOIN但是我们可以对左连接和右连接的结果做UNION操作来实现。
```
SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id
UNION
SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;
+------+-------+--------+------+------------+
| id | title | typeId | id | name |
+------+-------+--------+------+------------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 8 | hhh | NULL | NULL | NULL |
| 9 | iii | NULL | NULL | NULL |
| 10 | jjj | NULL | NULL | NULL |
| NULL | NULL | NULL | 5 | Javascript |
+------+-------+--------+------+------------+
```
## 6. USING子句
MySQL中连接SQL语句中ON子句的语法格式为table1.column_name = table2.column_name。当模式设计对联接表的列采用了相同的命名样式时就可以使用 USING 语法来简化 ON 语法格式为USING(column_name)。
所以USING的功能相当于ON区别在于USING指定一个属性名用于连接两个表而ON指定一个条件。另外SELECT *时USING会去除USING指定的列而ON不会。实例如下。
```sql
SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId =t_type.id;
+----+-------+--------+----+------+
| id | title | typeId | id | name |
+----+-------+--------+----+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
+----+-------+--------+----+------+
SELECT * FROM t_blog INNER JOIN t_type USING(typeId);
ERROR 1054 (42S22): Unknown column 'typeId' in 'from clause'
SELECT * FROM t_blog INNER JOIN t_type USING(id); -- 应为t_blog的typeId与t_type的id不同名无法用Using这里用id代替下。
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
+----+-------+--------+------------+
```
## 7. 自然连接NATURE JOIN
自然连接就是USING子句的简化版它找出两个表中相同的列作为连接条件进行连接。有左自然连接右自然连接和普通自然连接之分。在t_blog和t_type示例中两个表相同的列是id所以会拿id作为连接条件。 另外千万分清下面三条语句的区别 。
1. 自然连接:SELECT * FROM t_blog NATURAL JOIN t_type;
2. 笛卡尔积:SELECT * FROM t_blog NATURA JOIN t_type;
3. 笛卡尔积:SELECT * FROM t_blog NATURE JOIN t_type;
```sql
SELECT * FROM t_blog NATURAL JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog,t_type WHERE t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type USING(id);
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
+----+-------+--------+------------+
SELECT * FROM t_blog NATURAL LEFT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type USING(id);
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
| 6 | fff | 3 | NULL |
| 7 | ggg | 2 | NULL |
| 8 | hhh | NULL | NULL |
| 9 | iii | NULL | NULL |
| 10 | jjj | NULL | NULL |
+----+-------+--------+------------+
SELECT * FROM t_blog NATURAL RIGHT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type USING(id);
+----+------------+-------+--------+
| id | name | title | typeId |
+----+------------+-------+--------+
| 1 | C++ | aaa | 1 |
| 2 | C | bbb | 2 |
| 3 | Java | ccc | 3 |
| 4 | C# | ddd | 4 |
| 5 | Javascript | eee | 4 |
+----+------------+-------+--------+
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,4 +1,5 @@
## 1 数据库基本对象
## 1 数据库基本对象DDL
(数据库定义语言)
> sql 不提供修改模式定义和修改视图定义的操作。如果想要更改,智能删除重建。
> 数据库中操作的基本对象主要有以下内容:模式、表、视图、索引
@@ -14,7 +15,7 @@
|字段COLUMN|ADD COLUMN | DROP COLUMN|ALTER COLUMN|
```
```mermaid
graph LR
模式Schema-->表Table
模式Schema-->视图View
@@ -51,13 +52,16 @@ graph LR
```
## 1.4 视图VIEW
### 定义
视图: view, 是一种不存在的虚拟表。视图有表结构。没有数据, 视图的数据来源都是基表。视图根据基表的数量分为两种:
* 单表视图: 基表只有一个
* 多表视图: 基表至少两个以
### 视图作用
1. 能够简化用户的操作
2. 是用户能够以多种角度看待同一个数据
3. 视图对重构数据库提供了一定程度的逻辑独立性
4. 能够对机密数据提供安全保护。针对不同用户定制特定的视图隐藏不必要的信息
5. 适当利用视图可以更清晰的表达查询。
### 作用
1. 能够简化用户的操作。视图可以将复杂的SQL查询语句进行了封装简化了SQL语句从而提升了带宽的使用效率和网络间的传输效率
2. 主要是为了对外提供数据支持(外部系统)。隐藏基表字段(隐私)。保证了数据库的数据安全(保护数据库内部的数据结构)。可以灵活的控制对外的数据,保证针对每个接口都有一个单独的数据支持,增强了用户友好性.。
3. 视图利于权限控制: 有助于数据库对权限进行管理
### 创建
```
@@ -65,6 +69,12 @@ CREATE VIEW <视图名>[列名,列名,...]
AS <子查询>
[WITH CHECK OPTION];
```
视图创建的结果
* 会在对应的数据库的表空间中产生一个视图(表)
* 会在数据库对应存储文件夹下产生一个结构文件
1. 单基本表视图
```
# 行列子集视图-单个基本表视图。示例(建立信息系学生的视图)
@@ -124,9 +134,12 @@ FROM IS_Student
WHERE Sage<20;
```
> 查询数据的原理过程——视图消解:对视图进行查询时,首先先进行有效性检查,检查查询中设计的表、视图是否存在;如果存在,则从数据字典中取出视图的定义,把定义中的子查询和用户的查询结合起来,转化成等价的对基本表的查询,然后再执行修正了的查询。
> 查询数据的原理过程——
> 1. 视图检查:对视图进行查询时,首先先进行有效性检查,检查查询中设计的表、视图是否存在;
> 2. 视图消解:如果存在,则从数据字典中取出视图的定义,把定义中的子查询和用户的查询结合起来,转化成等价的对基本表的查询。
> 3. 然后再执行修正了的查询。
> 视图与派生表的区别:视图一旦定义,将永久保存在数据字典中,之后的所有查询都可以直接引用该视图。派生表知识在语句执行临时定义,语句执行后,该定义被删除。
> 视图与派生表的区别:视图一旦定义,将永久保存在数据字典中,之后的所有查询都可以直接引用该视图。派生表只是 在语句执行临时定义,语句执行后,该定义被删除。
2. 插入数据
@@ -182,7 +195,8 @@ WHERE Sno='2012'
## 1.5 索引INDEX
### 简介
### 索引类型
* 顺序文件上的索引:按指定属性升序或降序存储的关系。在属性上建立一个顺序索引文件,索引文件有属性值和响应的元组指针组成。
* B+树上的索引B+树的叶节点为属性值和相应的元组指针。具有动态平衡的特点。
@@ -219,9 +233,10 @@ ALTER INDEX <旧索引名> RENAME TO <新索引名>
ALTER INDEX SCno RENAME TO SCSno
```
## 2 数据库数据对象
## 2 数据库数据对象操作DML
(数据库操作语言)
## 2.1 增加
## 2.1 插入
1. 插入元组(一条或多条数据)
```
@@ -315,11 +330,12 @@ WHERE Sno IN
WHERE Sdept='CS')
```
## 2.4 查询
## 3 数据库数据对象查询DQL
(数据库查询语言)
### 2.4.1 单表查询
## 3.1 单表查询
1. 查询多列
### 1. 查询多列
```
@@ -337,7 +353,7 @@ SELECT LOWER(Sdept) FROM Student;
SELECT Sname NAME,2014-Sage BIRTHDAY,LOWER(Sdept) DEPARTMENT FROM Student
```
2. 查询元组
### 2. 范围查询
|查询条件|谓词|
|-|-|
@@ -371,7 +387,7 @@ SELECT Sname FROM Student
WHERE Sdept='CS' AND Sage<20
```
3. 查询排序
### 3. 查询排序
|关键字|含义|
|-|-|
@@ -385,7 +401,7 @@ ORDER BY Grade DESC;
```
> 空值的排序根据具体的系统来决定。
4. 聚集函数
### 4. 聚集函数
|聚集函数|功能|
|-|-|
@@ -398,7 +414,7 @@ MIN(DISTINCT/All 列)|求最小值|
> 聚集函数遇到空值是出了COUNT(*)外,都跳过空值,处理非空值。
5. 分组查询
### 5. 分组查询
> GROUP BY 子句将查询结果按某一列或多列的值分组,值相等的为一组。
```
@@ -413,11 +429,11 @@ HAVING COUNT(*)>3
> WHERE 子句作用于基本表或视图HAVING短语作用于组从中选择满足条件的组也就是分组条件。
### 2.4.2 连接查询(多表查询)
## 3.2 连接查询(多表查询)
> 本质上是将多个表通过一些条件拼接成一张表。
1. 等值与非等值连接查询
### 1. 等值与非等值连接查询
```
SELECT Student.*,SC.*
@@ -427,7 +443,7 @@ WHERE Student.Sno=SC.Sno
> 嵌套循环连接查询的基本步骤首先扫描第一个表的第一个元组然后从头扫描SC查找满足条件的元组就进行拼接。
2. 自身连接查询
### 2. 自身连接查询
> 为自身的表取两个不同的别名。
@@ -437,7 +453,7 @@ FROM Course first,Course second
WHERE first.Cpno=second.Cno;
```
3. 外连接查询
### 3. 外连接查询
> 与等值连接不同的地方在于,有一张主表,主表中所有的元组会保留。而在等值连接中,两张表的地位是等价的,一张表中没有被连接的元素就回清除。
@@ -446,7 +462,7 @@ SELECT student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT OUTER JOIN SC on(Student.Sno=SC.Sno);
```
4. 多表连接查询
### 4. 多表连接查询
> 涉及两个或两个以上的表进行连接。我个人来说,如果嵌套查询和连接查询都能够实现时,我更喜欢连接查询。
```
SELECT Student.Sno,Sname,Cname,Grade
@@ -454,17 +470,17 @@ FROM Student,SC,Cource
WHERE Student.Sno=SC.Sno AND SC.Cno=Course.Cno
```
### 2.4.3 嵌套查询
## 3.3 嵌套查询(子查询)
> 一个SELECT FROM WHERE语句成为一个查询快。将一个查询快嵌套在另一个查询块的WHERE子句或HAVING短语的条件中称为嵌套查询。用户可以用多个简单查询构造成负责的查询层层嵌套的结构化查询。
> 子查询的查询条件依赖于父查询,称为相关子查询。
> 子查询的查询条件依赖于父查询,称为相关子查询。相关子查询不能一次求得查询结果,内层查询依赖外层查询的值,必须在内层查询与外层查询之间反复求值。
* 一个SELECT FROM WHERE语句成为一个查询快。将一个查询快嵌套在另一个查询块的WHERE子句或HAVING短语的条件中称为嵌套查询。用户可以用多个简单查询构造成负责的查询层层嵌套的结构化查询。
* 子查询的查询条件不依赖于父查询,称为**不相关子查询**。
* 子查询的查询条件依赖于父查询,称为**相关子查询**
* 相关子查询不能一次求得查询结果,内层查询依赖外层查询的值,必须在内层查询与外层查询之间反复求值。
> 有些嵌套查询可以用连接运算替代,有些事不能替代的。
1. 带有IN谓词的子查询
### 1. 带有IN谓词的子查询
```
# 不相关子查询_选修了“信息系统”的学生姓名
SELECT Sno,Sname
@@ -481,7 +497,7 @@ WHERE Sno IN
```
2. 带有比较运算的子查询
### 2. 带有比较运算的子查询
```
# 相关子查询_超过自己选修课程平均成绩的课程号
@@ -492,7 +508,7 @@ WHERE Grade>=(SELECT AVG(Grade)
WHERE y.Sno=x.Sno);
```
3. 带有ANY、SOME、ALL谓词的子查询
### 3. 带有ANY、SOME、ALL谓词的子查询
> 谓词说明,都能使用聚集函数来表示。
@@ -515,7 +531,7 @@ WHERE Grade>=(SELECT AVG(Grade)
```
4. 带有EXISTS谓词的子查询
### 4. 带有EXISTS谓词的子查询
> 代表存在量词,带有EXISTS谓词的子查询不反悔任何数据只产生逻辑真或假true&false。
@@ -538,7 +554,7 @@ WHERE Student.Sno IN
```
### 2.4.4 集合查询
## 3.4 集合查询
> 因为查询结果是元组的集合所以多个SELECT语句的结果可进行集合操作。集合操作包括UNION、INTERSECT、EXCEPT。
@@ -569,7 +585,7 @@ WHERE Sage<=19
```
### 2.4.5 基于派生表的查询
## 3.5 基于派生表的查询
> 子查询不仅可以出现在WHERE子句中还可以出现在FROM子句中这是子查询生成的临时派生表成为主查询的对象。
@@ -582,36 +598,25 @@ WHERE SC.sno=avg_sc.avg_sno and SC.Grade >= AVG_sc.avg_grade
> 必须为派生关系指定一个别名。
其实在普通的嵌套子查询当中或者链接查询当中,就是两个或者多个表之间的操作,对一个表的查询结果,作为一张中间表进行使用。
## 3 空值处理
## 4 空值处理
> 空值是不确定的、不知道的、不存在的或无意义的值
### 3.1 空值的产生
### 3.2 空值的判断
### 4.1 空值的产生
### 4.2 空值的判断
```
IS NULL //空值
IS NOT NULL// 非空值
```
### 3.3 空值的约束条件
### 4.3 空值的约束条件
* 如果在字段定义中使用了NOT NULL、Primary Key、UNIQUE则该属性不能使用空值。
### 3.4 空值的算术晕眩、比较运算和逻辑运算
### 4.4 空值的算术运算、比较运算和逻辑运算
* 空值与另一个值的算术运算结果为空值。
* 空值与另外一个值的比较运算结果为UNKNOWN
* 空值与另外一个值的逻辑运算遵循逻辑运算的法则。如果需要考虑空值则为UNKNOWN如果不虚要考虑空值就能得到结果则为已经得到的结果。

View File

@@ -1,3 +1,4 @@
# 完整性
> 引言:
> 广义上,人们习惯将完整性分为三类:实体完整性、参照完整性、用户自定义完整性。(课本上如此定义)

View File

@@ -1,37 +1,26 @@
# 并发控制
> 参考文献
>
> - [锁与并发](https://www.cnblogs.com/luyucheng/p/6297752.html)
## 1 事务
## 1 事务的特性
### 概念
事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222237925.png"/> </div><br>
![](image/2021-09-01-16-09-35.png)
### ACID
#### 1. 原子性Atomicity
1. 原子性Atomicity事务被视为不可分割的最小单元事务的所有操作要么全部提交成功要么全部失败回滚。回滚可以用回滚日志Undo Log来实现回滚日志记录着事务所执行的修改操作在回滚时反向执行这些修改操作即可。
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚
2. 一致性Consistency数据库在事务执行前后都保持一致性状态。在一致性状态下所有事务对同一个数据的读取结果都是相同的
回滚可以用回滚日志Undo Log来实现回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可
3. 隔离性Isolation一个事务所的修改在最终提交以前,对其它事务是不可见的
#### 2. 一致性Consistency
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。
#### 3. 隔离性Isolation
一个事务所做的修改在最终提交以前,对其它事务是不可见的。
#### 4. 持久性Durability
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
系统发生崩溃可以用重做日志Redo Log进行恢复从而实现持久性。与回滚日志记录数据的逻辑修改不同重做日志记录的是数据页的物理修改。
----
4. 持久性Durability一旦事务提交则其所做的修改将会永远保存到数据库中。即使系统发生崩溃事务执行的结果也不能丢失。系统发生崩溃可以用重做日志Redo Log进行恢复从而实现持久性。与回滚日志记录数据的逻辑修改不同重做日志记录的是数据页的物理修改。
事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:
@@ -40,283 +29,76 @@
- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
- 事务满足持久化是为了能应对系统崩溃的情况。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207210437023.png"/> </div><br>
![](image/2021-09-01-16-19-37.png)
### AUTOCOMMIT
MySQL 默认采用自动提交模式。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。
## 2并发一致性问题
## 2 并发一致性问题
在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
### 丢失修改
1. 脏读Dirty Reads一个事务可以读取另一个尚未提交事务的修改数据 。
2. 不可重复读Non-Repeatable Reads同一个事务中多次查询某个数据却得到不同的结果。
3. 幻影读Phantom Reads一个事务按相同的查询条件重新读取以前检索过的数据却发现其他事务插入了满足其查询条件的新数据。
丢失修改指一个事务的更新操作被另外一个事务的更新操作替换。一般在现实生活中常会遇到例如T<sub>1</sub> 和 T<sub>2</sub> 两个事务都对一个数据进行修改T<sub>1</sub> 先修改并提交生效T<sub>2</sub> 随后修改T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221744244.png"/> </div><br>
### 读脏数据
### 脏读
读脏数据指在不同的事务下当前事务可以读到另外事务未提交的数据。例如T<sub>1</sub> 修改一个数据但未提交T<sub>2</sub> 随后读取这个数据。如果 T<sub>1</sub> 撤销了这次修改,那么 T<sub>2</sub> 读取的数据是脏数据。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221920368.png"/> </div><br>
![](image/2021-09-01-17-06-42.png)
### 不可重复读
不可重复读指在一个事务内多次读取同一数据集合。在这一事务还未结束前另一事务也访问了该同一数据集合并做了修改由于第二个事务的修改第一次事务的两次读取的数据可能不一致。例如T<sub>2</sub> 读取一个数据T<sub>1</sub> 对该数据做了修改。如果 T<sub>2</sub> 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222102010.png"/> </div><br>
![](image/2021-09-01-17-06-54.png)
### 幻影读
幻读本质上也属于不可重复读的情况T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插入新的数据T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
- 事务 A 按照一定条件进行数据读取, 期间事务 B 插入了相同搜索条件的新数据,事务 A 再次按照原先条件进行读取时,发现了事务 B 新插入的数据 称为幻读。幻读本质上也属于不可重复读的情况T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插入新的数据T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222134306.png"/> </div><br>
产生影响:
----
- 会造成一个事务中先产生的锁,无法锁住后加入的满足条件的行。
- 产生数据一致性问题,在一个事务中,先对符合条件的目标行做变更,而在事务提交前有新的符合目标条件的行加入。这样通过 binlog 恢复的数据是会将所有符合条件的目标行都进行变更的。
产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
产生原因:
## 3 封锁
- 行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录。
### 封锁粒度
应对方法
MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
- 将两行记录间的空隙加上锁,阻止新记录的插入;这个锁称为间隙锁。
- 间隙锁与间隙锁之间没有冲突关系。跟间隙锁存在冲突关系的,是往这个间隙中插入一个记录这个操作。
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
![](image/2021-09-01-17-07-04.png)
但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大
> 产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题
在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。
## 3 事务隔离级别
### 解决方案
### 封锁类型
“脏读”、“不可重复读”和“幻读”,其实都是数据库**读一致性问题**,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为以下两种:
#### 1. 读写锁
1. 多级锁协议。在读取数据前,对其加锁,阻止其他事务对数据进行修改。
2. 多版本并发控制MultiVersion Concurrency ControlMVCC。不用加任何锁通过一定机制生成一个数据请求时间点的一致性数据快照Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。这种技术也经常称为多版本数据库。
- 互斥锁Exclusive简写为 X 锁,又称写锁。
- 共享锁Shared简写为 S 锁,又称读锁。
### 隔离级别
有以下两个规定:
> MySQL 中默认的事务处理级别就是**REPEATABLE-READ 可重复读**。
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁
1. 未提交读READ UNCOMMITTED事务中的修改即使没有提交对其它事务也是可见的。允许脏读
2. 提交读READ COMMITTED一个事务只能读取已经提交的事务所做的修改。换句话说一个事务所做的修改在提交之前对其它事务是不可见的
3. 可重复读REPEATABLE READ保证在同一个事务中多次读取同一数据的结果是一样的。
4. 可串行化SERIALIZABLE强制事务串行执行这样多个事务互不干扰不会出现并发一致性问题。该隔离级别需要加锁实现因为要使用加锁机制保证同一时间只有一个事务执行也就是保证事务串行执行。
锁的兼容关系如下:
![](image/2021-09-01-17-20-31.png)
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207213523777.png"/> </div><br>
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。
#### 2. 意向锁
定义了 4 个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。
使用意向锁Intention Locks可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
意向锁在原来的 X/S 锁之上引入了 IX/ISIX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
各种锁的兼容关系如下:
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207214442687.png"/> </div><br>
解释如下:
- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;
- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T<sub>1</sub> 想要对数据行 R<sub>1</sub> 加 X 锁,事务 T<sub>2</sub> 想要对同一个表的数据行 R<sub>2</sub> 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)
### 封锁协议
#### 1. 三级封锁协议
**一级封锁协议**
事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207220440451.png"/> </div><br>
**二级封锁协议**
在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207220831843.png"/> </div><br>
**三级封锁协议**
在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221313819.png"/> </div><br>
#### 2. 两段锁协议
加锁和解锁分为两个阶段进行。
可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。串行执行的事务互不干扰,不会出现并发一致性问题。
事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
```html
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
```
但不是必要条件,例如以下操作不满足两段锁协议,但它还是可串行化调度。
```html
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
```
### MySQL 隐式与显示锁定
MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB 也可以使用特定的语句进行显示锁定:
```sql
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
```
## 4 隔离级别
### 未提交读READ UNCOMMITTED
事务中的修改,即使没有提交,对其它事务也是可见的。
### 提交读READ COMMITTED
一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
### 可重复读REPEATABLE READ
保证在同一个事务中多次读取同一数据的结果是一样的。
### 可串行化SERIALIZABLE
强制事务串行执行,这样多个事务互不干扰,不会出现并发一致性问题。
该隔离级别需要加锁实现,因为要使用加锁机制保证同一时间只有一个事务执行,也就是保证事务串行执行。
----
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207223400787.png"/> </div><br>
## 5 多版本并发控制
多版本并发控制Multi-Version Concurrency Control, MVCC是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁单纯使用 MVCC 无法实现。
### 基本思想
在封锁一节中提到,加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的,而 MVCC 利用了多版本的思想,写操作更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系,这一点和 CopyOnWrite 类似。
在 MVCC 中事务的修改操作DELETE、INSERT、UPDATE会为数据行新增一个版本快照。
脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改。在事务进行读取操作时为了解决脏读和不可重复读问题MVCC 规定只能读取已经提交的快照。当然一个事务可以读取自身未提交的快照,这不算是脏读。
### 版本号
- 系统版本号 SYS_ID是一个递增的数字每开始一个新的事务系统版本号就会自动递增。
- 事务版本号 TRX_ID :事务开始时的系统版本号。
### Undo 日志
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
例如在 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 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208164808217.png"/> </div><br>
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。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208171445674.png"/> </div><br>
在进行 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需要根据隔离级别再进行判断
- 提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
- 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。
在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
### 快照读与当前读
#### 1. 快照读
MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
```sql
SELECT * FROM table ...;
```
#### 2. 当前读
MVCC 其它会对数据库进行修改的操作INSERT、UPDATE、DELETE需要进行加锁操作从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。
```sql
INSERT;
UPDATE;
DELETE;
```
在进行 SELECT 操作时,可以强制指定进行加锁操作。以下第一个语句需要加 S 锁,第二个需要加 X 锁。
```sql
SELECT * FROM table WHERE ? lock in share mode;
SELECT * FROM table WHERE ? for update;
```
## 6 Next-Key Locks
Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。
MVCC 不能解决幻影读问题Next-Key Locks 就是为了解决这个问题而存在的。在可重复读REPEATABLE READ隔离级别下使用 MVCC + Next-Key Locks 可以解决幻读问题。
### Record Locks
锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。
### Gap Locks
锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
```sql
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
```
### Next-Key Locks
它是 Record Locks 和 Gap Locks 的结合不仅锁定一个记录上的索引也锁定索引之间的间隙。它锁定一个前开后闭区间例如一个索引包含以下值10, 11, 13, and 20那么就需要锁定以下区间
```sql
(-, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +)
```
![](image/2021-09-01-17-29-05.png)

View File

@@ -132,7 +132,19 @@ Sno -> Sdept -> Mname
| 学院-1 | 院长-1 |
| 学院-2 | 院长-2 |
### BCNF巴斯德范式
巴斯-科德范式即在满足第三范式3NF基础上任何非主属性不能对主键子集依赖即在3NF基础上消除主属性对候选码的部分函数依赖和传递函数依赖
BC范式既检查非主属性又检查主属性。当只检查非主属性时就成了第三范式。满足BC范式的关系都必然满足第三范式。或者还可以换一种说法若一个关系达到了第三范式并且它只有一个候选码或者它的每个候选码都是单属性则该关系自然达到BC范式。
一般来说一个数据库设计符合3NF或BCNF就可以了。
### 第4范式4NF
* 多值依赖的概念:多值依赖即属性之间的一对多关系记为K→→A。函数依赖事实上是单值依赖所以不能表达属性值之间的一对多关系。
* 平凡的多值依赖全集U=K+A一个K可以对应于多个A即K→→A。此时整个表就是一组一对多关系。
* 非平凡的多值依赖全集U=K+A+B一个K可以对应于多个A也可以对应于多个BA与B互相独立即K→→AK→→B。整个表有多组一对多关系且有“一”部分是相同的属性集合“多”部分是互相独立的属性集合。
第四范式即在满足巴斯-科德范式BCNF的基础上消除非平凡且非函数依赖的多值依赖(**即把同一表内的多对多关系删除**)。
### 总结
* 1NF 字段是最小的的单元不可再分