|
|
|
@ -959,7 +959,7 @@ kill trx_mysql_thread_id;
|
|
|
|
|
|
|
|
|
|
事务是一系列对系统中数据进行访问与更新的操作组成的一个程序逻辑单元。即不可分割的许多基础数据库操作。
|
|
|
|
|
|
|
|
|
|
## 事务特性(ACID)
|
|
|
|
|
## 事务特性(ACID)
|
|
|
|
|
|
|
|
|
|
### 原子性(Atomicity)
|
|
|
|
|
|
|
|
|
@ -1311,219 +1311,120 @@ SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
|
|
|
|
|
|
|
|
|
|
## 索引结构
|
|
|
|
|
|
|
|
|
|
### 平衡多路查找树(B-Tree)
|
|
|
|
|
### Hash索引
|
|
|
|
|
|
|
|
|
|
二叉查找树查询的时间复杂度是O(logN),查找速度最快和比较次数较少。但用于数据库索引,当数据量过大,不可能将所有索引加载进内存,使用二叉树会导致磁盘IO过于频繁,最坏的情况下磁盘IO的次数由树的高度来决定。
|
|
|
|
|
![Hash索引](images/Database/Hash索引.png)
|
|
|
|
|
|
|
|
|
|
B-Tree对二叉树进行了横向扩展,使树结构更加**矮胖**,使得一次IO能加载更多关键字,对比在内存中完成,减少了磁盘IO次数,更适用于大型数据库,但是为了保持自平衡,插入或者删除元素都会导致节点发生裂变反应,有时候会非常麻烦。
|
|
|
|
|
|
|
|
|
|
![索引机制-B-树](images/Database/索引机制-B-树.jpg)
|
|
|
|
|
|
|
|
|
|
B-树的特性:
|
|
|
|
|
|
|
|
|
|
- 关键字集合分布在整颗树中
|
|
|
|
|
- 任何一个关键字出现且只出现在一个结点中
|
|
|
|
|
- 搜索有可能在非叶子结点结束
|
|
|
|
|
- 其搜索性能等价于在关键字全集内做一次二分查找
|
|
|
|
|
- 自动层次控制
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### B+Tree
|
|
|
|
|
|
|
|
|
|
B+树是B-树的变体,也是一种多路搜索树。其定义基本与B-树相同,除了:
|
|
|
|
|
|
|
|
|
|
- 非叶子结点的子树指针与关键字个数相同
|
|
|
|
|
- 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
|
|
|
|
|
- 为所有叶子结点增加一个链指针
|
|
|
|
|
- 所有关键字都在叶子结点出现
|
|
|
|
|
|
|
|
|
|
![索引机制-B+树](images/Database/索引机制-B+树.jpg)
|
|
|
|
|
|
|
|
|
|
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找。
|
|
|
|
|
|
|
|
|
|
B+的特性:
|
|
|
|
|
|
|
|
|
|
- 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的
|
|
|
|
|
- 不可能在非叶子结点命中
|
|
|
|
|
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
|
|
|
|
|
- 更适合文件索引系统
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### B*Tree
|
|
|
|
|
|
|
|
|
|
是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
|
|
|
|
|
|
|
|
|
|
![索引机制-B星树](images/Database/索引机制-B星树.jpg)
|
|
|
|
|
|
|
|
|
|
B*树定义了非叶子结点关键字个数至少为(2/3)M,即块的最低使用率为2/3(代替B+树的1/2)。
|
|
|
|
|
|
|
|
|
|
**B+树的分裂**
|
|
|
|
|
|
|
|
|
|
当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。
|
|
|
|
|
|
|
|
|
|
**B*树的分裂**
|
|
|
|
|
|
|
|
|
|
当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B*树分配新结点的概率比B+树要低,空间使用率更高。
|
|
|
|
|
**原理**
|
|
|
|
|
|
|
|
|
|
- 事先将索引通过 hash算法后得到的hash值(即磁盘文件指针)存到hash表中
|
|
|
|
|
- 在进行查询时,将索引通过hash算法,得到hash值,与hash表中的hash值比对。通过磁盘文件指针,只要**一次磁盘IO**就能找到要的值
|
|
|
|
|
|
|
|
|
|
例如:在第一个表中,要查找col=6的值。hash(6) 得到值,比对hash表,就能得到89。性能非常高。
|
|
|
|
|
|
|
|
|
|
### 二叉树
|
|
|
|
|
|
|
|
|
|
**特点**
|
|
|
|
|
|
|
|
|
|
1. 左子节点值 < 节点值
|
|
|
|
|
2. 右子节点值 > 节点值
|
|
|
|
|
3. 当数据量非常大时,要查找的数据又非常靠后,和没有索引相比,那么二叉树结构的查询优势将非常明显
|
|
|
|
|
|
|
|
|
|
**存在问题**
|
|
|
|
|
|
|
|
|
|
如下图,可以看出,二叉树出现单边增长时,二叉树变成了“链”,这样查找一个数的时候,速度并没有得到很大的优化。
|
|
|
|
|
|
|
|
|
|
![索引结构-二叉树](images/Database/索引结构-二叉树.png)
|
|
|
|
|
|
|
|
|
|
- 但是hash表索引存在问题,如果要查询带范围的条件时,hash索引就歇菜了
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 红黑树
|
|
|
|
|
|
|
|
|
|
**特点**
|
|
|
|
|
|
|
|
|
|
1. 节点是红色或者黑色
|
|
|
|
|
2. 根节点是黑色
|
|
|
|
|
3. 每个叶子的节点都是黑色的空节点(NULL)
|
|
|
|
|
4. 每个红色节点的两个子节点都是黑色的
|
|
|
|
|
5. 从任意节点到其每个叶子的所有路径都包含相同的黑色节点
|
|
|
|
|
**优点**
|
|
|
|
|
|
|
|
|
|
![索引结构-红黑树](images/Database/索引结构-红黑树.png)
|
|
|
|
|
- 快速查询:参与索引的字段只要进行Hash运算之后就可以快速定位到该记录,时间复杂度约为1
|
|
|
|
|
|
|
|
|
|
**存在的问题**
|
|
|
|
|
**缺点**
|
|
|
|
|
|
|
|
|
|
**红黑树虽然和二叉树相比,一定程度上缓解了单边过长的问题,但是它依旧存储高度问题。**
|
|
|
|
|
- 哈希索引只包含哈希值和行指针,所以不能用索引中的值来避免读取行
|
|
|
|
|
- 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序和范围查询
|
|
|
|
|
- 哈希索引也不支持部分索引列查询,因为哈希索引始终是使用索引列的全部数据进行哈希计算的
|
|
|
|
|
- 哈希索引只支持等值比较查询,如=,IN(),<=>操作
|
|
|
|
|
- 如果哈希冲突较多,一些索引的维护操作的代价也会更高
|
|
|
|
|
|
|
|
|
|
假设现在数据量有100万,那么红黑树的高度大概为 100,0000 = 2^n, n大概为 20。那么,至少要20次的磁盘IO,这样,性能将很受影响。如果数据量更大,IO次数更多,性能损耗更大。**所以红黑树依旧不是最佳方案。**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### B-Tree索引
|
|
|
|
|
|
|
|
|
|
**思考:针对上面的红黑树结构,我们能否优化一下呢?**
|
|
|
|
|
**背景**:二叉查找树查询的时间复杂度是O(logN),查找速度最快和比较次数较少。但用于数据库索引,当数据量过大,不可能将所有索引加载进内存,使用二叉树会导致磁盘IO过于频繁,最坏的情况下磁盘IO的次数由树的高度来决定。
|
|
|
|
|
|
|
|
|
|
上述红黑树默认一个节点就存了一个 (索引+磁盘地址),我们设想一个节点存多个 (索引+磁盘地址),这样就可以降低红黑树的高度了。 **实际上我们设想的这种结构就是 B-Tree**。
|
|
|
|
|
B-Tree(平衡多路查找树)对二叉树进行了横向扩展,能很好解决红黑树中遗留的高度问题,使树结构更加**矮胖**,使得一次IO能加载更多关键字,对比在内存中完成,减少了磁盘IO次数,更适用于大型数据库,但是为了保持自平衡,插入或者删除元素都会导致节点发生裂变反应,有时候会非常麻烦。
|
|
|
|
|
|
|
|
|
|
![索引结构-B-Tree](images/Database/索引结构-B-Tree.png)
|
|
|
|
|
|
|
|
|
|
**案例分析**:模拟下查找key为29的data的过程
|
|
|
|
|
|
|
|
|
|
### Hash
|
|
|
|
|
- 根据根结点指针读取文件目录的根磁盘块1。【磁盘IO操作第**1次**】
|
|
|
|
|
- 磁盘块1存储17,35和三个指针数据。我们发现17<29<35,因此我们找到指针p2
|
|
|
|
|
- 根据p2指针,我们定位并读取磁盘块3。【磁盘IO操作**2次**】
|
|
|
|
|
- 磁盘块3存储26,30和三个指针数据。我们发现26<29<30,因此我们找到指针p2
|
|
|
|
|
- 根据p2指针,我们定位并读取磁盘块8。【磁盘IO操作**3次**】
|
|
|
|
|
- 磁盘块8中存储28,29。我们找到29,获取29所对应的数据data
|
|
|
|
|
|
|
|
|
|
**原理**
|
|
|
|
|
|
|
|
|
|
1. 事先将索引通过 hash算法后得到的hash值(即磁盘文件指针)存到hash表中
|
|
|
|
|
2. 在进行查询时,将索引通过hash算法,得到hash值,与hash表中的hash值比对。通过磁盘文件指针,只要**一次磁盘IO**就能找到要的值
|
|
|
|
|
|
|
|
|
|
例如:在第一个表中,要查找col=6的值。hash(6) 得到值,比对hash表,就能得到89。性能非常高。
|
|
|
|
|
|
|
|
|
|
**存在问题**
|
|
|
|
|
|
|
|
|
|
但是hash表索引存在问题,如果要查询带范围的条件时,hash索引就歇菜了。
|
|
|
|
|
|
|
|
|
|
```mysql
|
|
|
|
|
select *from t where col1>=6;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### B-Tree
|
|
|
|
|
|
|
|
|
|
**特点**
|
|
|
|
|
|
|
|
|
|
B-Tree索引能很好解决红黑树中遗留的高度问题,B-Tree 是一种平衡的多路查找(又称排序)树,在文件系统中和数据库系统有所应用,主要用作文件的索引,其中的B就表示平衡(Balance)。
|
|
|
|
|
|
|
|
|
|
为了描述B-Tree,首先定义一条数据记录为一个二元组 [key, data],key为记录的键值key,对于不同数据记录,key是互不相同的;**data为数据记录除以key外的数据 (这里指的是聚集索引)**。那么B-Tree是满足下列条件的数据结构:
|
|
|
|
|
|
|
|
|
|
1. d 为大于1的一个正整数,称为BTree的度
|
|
|
|
|
2. h为一个正整数,称为BTree的高度
|
|
|
|
|
3. key和指针互相间隔,节点两端是指针
|
|
|
|
|
4. 叶子节点具有相同的深度,叶子节点的指针为空,节点中数据索引(下图中的key)从左往右递增排列
|
|
|
|
|
- **对范围查找没有更简单的方法**。可以用B+Tree解决
|
|
|
|
|
|
|
|
|
|
**说明**:下图都是以主键索引为例,至于非主键索引(非聚集索引),无非就是data里存的内容不同。
|
|
|
|
|
- **每行数据量很大时,会导致B-Tree深度较大,进而影响查询效率**。可以用B+Tree解决
|
|
|
|
|
|
|
|
|
|
![索引结构-B-Tree指针](images/Database/索引结构-B-Tree指针.png)
|
|
|
|
|
|
|
|
|
|
![索引结构-B-Tree](images/Database/索引结构-B-Tree.png)
|
|
|
|
|
|
|
|
|
|
**分析**
|
|
|
|
|
|
|
|
|
|
模拟下查找key为29的data的过程:
|
|
|
|
|
### B+Tree索引
|
|
|
|
|
|
|
|
|
|
1. 根据根结点指针读取文件目录的根磁盘块1。【磁盘IO操作第**1次**】
|
|
|
|
|
2. 磁盘块1存储17,35和三个指针数据。我们发现17<29<35,因此我们找到指针p2
|
|
|
|
|
3. 根据p2指针,我们定位并读取磁盘块3。【磁盘IO操作**2次**】
|
|
|
|
|
4. 磁盘块3存储26,30和三个指针数据。我们发现26<29<30,因此我们找到指针p2
|
|
|
|
|
5. 根据p2指针,我们定位并读取磁盘块8。【磁盘IO操作**3次**】
|
|
|
|
|
6. 磁盘块8中存储28,29。我们找到29,获取29所对应的数据data
|
|
|
|
|
|
|
|
|
|
**存在问题**
|
|
|
|
|
|
|
|
|
|
1. 比如,下面查询语句,那么不但需要叶子节点>20的值,也需要非叶子节点在右边节点的值。即下图画圈的两部分, **B-Tree似乎在范围查找没有更简便的方法,为了解决这一问题。我们可以用B+Tree。**
|
|
|
|
|
|
|
|
|
|
```mysql
|
|
|
|
|
select *from t where col1 > 20;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
![索引结构-B-Tree](images/Database/索引结构-B-Tree问题.png)
|
|
|
|
|
|
|
|
|
|
2. 深度问题
|
|
|
|
|
|
|
|
|
|
从图上可以看到,每个节点中不仅包含数据的key值,还有data值。而每一个节点的存储空间是有限的(mysql默认设置一个节点的大小为16K),如果data中存放的数据较大时,将会导致每个节点(即一个页)能存储的key的数量(索引的数量)很小,所以当数据量很多,且每行数据量很大的时候,同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。所以引入B+Tree
|
|
|
|
|
B+树是B-树的变体,也是一种多路搜索树。其定义基本与B-树相同,除了:
|
|
|
|
|
|
|
|
|
|
- 非叶子结点的子树指针与关键字个数相同
|
|
|
|
|
- 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
|
|
|
|
|
- 为所有叶子结点增加一个链指针
|
|
|
|
|
- 所有关键字都在叶子结点出现
|
|
|
|
|
|
|
|
|
|
![索引结构-B+Tree](images/Database/索引结构-B+Tree.png)
|
|
|
|
|
|
|
|
|
|
### B+Tree
|
|
|
|
|
**剖析**:如上图,在叶子节点上注意是MySQL已经有成双向箭头(原生B+Tree是单向的),而且从左到右是递增顺序的,所以很好的解决了 > 和 < 这类查找问题。
|
|
|
|
|
|
|
|
|
|
**特点**
|
|
|
|
|
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找。
|
|
|
|
|
|
|
|
|
|
`B+Tree`是在`B-Tree`基础上的一种优化,使其更适合实现外存储索引结构。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
|
|
|
|
|
B+的特性:
|
|
|
|
|
|
|
|
|
|
1. 非叶子节点不存储data,只存储索引,可以存放更多索引
|
|
|
|
|
2. 叶子节点不存储指针
|
|
|
|
|
3. 顺序访问指针,提高区间访问性能
|
|
|
|
|
4. 非叶子节点中的索引最终还是会在叶子节点上存储一份,也就是叶子节点会包含非叶子节点上的所有索引
|
|
|
|
|
5. 一个父节点,它的**左侧**子节点都**小于**父节点的值,**右侧**的子节点都**大于等于**父节点的值
|
|
|
|
|
6. 每一层节点从左往右都是递增排列,无论是数值型还是字符型
|
|
|
|
|
- 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的
|
|
|
|
|
- 不可能在非叶子结点命中
|
|
|
|
|
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
|
|
|
|
|
- 更适合文件索引系统
|
|
|
|
|
|
|
|
|
|
**注意**:MySQL索引默认的存储结构使用的就是B+Tree。
|
|
|
|
|
|
|
|
|
|
**![索引结构-B+Tree指针](images/Database/索引结构-B+Tree指针.png)**
|
|
|
|
|
|
|
|
|
|
![索引结构-B+Tree](images/Database/索引结构-B+Tree.png)
|
|
|
|
|
**优点**
|
|
|
|
|
|
|
|
|
|
**剖析**:如上图,在叶子节点上注意是MySQL已经有成双向箭头(原生B+Tree是单向的),而且从左到右是递增顺序的,所以很好的解决了 > 和 < 这类查找问题。
|
|
|
|
|
- 单次请求涉及的磁盘IO次数少(出度d大,且非叶子节点不包含表数据,树的高度小)
|
|
|
|
|
- 查询效率稳定(任何关键字的查询必须走从根结点到叶子结点,查询路径长度相同)
|
|
|
|
|
- 遍历效率高(从符合条件的某个叶子节点开始遍历即可)
|
|
|
|
|
|
|
|
|
|
**分析**
|
|
|
|
|
**缺点**
|
|
|
|
|
|
|
|
|
|
假如:**以一个高度为3的B+Tree为例**,B+Tree的表都存满了,能存储多少数据?
|
|
|
|
|
B+树最大的性能问题在于会产生大量的随机IO,主要存在以下两种情况:
|
|
|
|
|
|
|
|
|
|
**首先,**查看MySQL默认一个节点页的大小:
|
|
|
|
|
- 主键不是有序递增的,导致每次插入数据产生大量的数据迁移和空间碎片
|
|
|
|
|
- 即使主键是有序递增的,大量写请求的分布仍是随机的
|
|
|
|
|
|
|
|
|
|
```mysql
|
|
|
|
|
SHOW GLOBAL STATUS like 'Innodb_page_size';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
如下图:大小为16K。
|
|
|
|
|
|
|
|
|
|
![索引结构-B+Tree案例](images/Database/索引结构-B+Tree案例.png)
|
|
|
|
|
### B*Tree
|
|
|
|
|
|
|
|
|
|
然后,假设主键Id为bigint类型,那么长度就是8B,指针在Innodb源码中大小为6B,所以一共就是14B,再假设最后一层,存放的数据data为1k 大小(能存很多内容了),那么:
|
|
|
|
|
是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
|
|
|
|
|
|
|
|
|
|
1. 第一层最大节点数为: 16k / (8B + 6B) = 1170 (个)
|
|
|
|
|
2. 第二层最大节点数也应为:1170个
|
|
|
|
|
3. 第三层最大节点数为:16k / 1k = 16 (个)
|
|
|
|
|
![索引机制-B星树](images/Database/索引机制-B星树.jpg)
|
|
|
|
|
|
|
|
|
|
则,一张B+Tree的表最多存放 1170 * 1170 * 16 = 21902400 ≈ 2千万。所以,通过分析,我们可以得出,B+Tree结构的表可以容纳千万数据量的查询。而且**一般来说,MySQL会把 B+Tree 根节点放在内存中**,那只需要**两次磁盘IO(第二层1次,第三层1次)**就行。
|
|
|
|
|
B*树定义了非叶子结点关键字个数至少为(2/3)M,即块的最低使用率为2/3(代替B+树的1/2)。
|
|
|
|
|
|
|
|
|
|
**扩展**
|
|
|
|
|
**B+树的分裂**
|
|
|
|
|
|
|
|
|
|
数据库中的B+Tree索引可以分为聚集索引(clustered index,也叫主键索引)和辅助索引(secondary index,也叫非聚集索引)。上面的B+Tree示例图在数据库中的实现对应的是聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据(除主键以外的所有数据),辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的对应的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。
|
|
|
|
|
当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。
|
|
|
|
|
|
|
|
|
|
**B*树的分裂**
|
|
|
|
|
|
|
|
|
|
当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B*树分配新结点的概率比B+树要低,空间使用率更高。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -2282,7 +2183,7 @@ redo log 实际的触发 fsync 操作写盘包含以下几个场景:
|
|
|
|
|
|
|
|
|
|
# SQL优化
|
|
|
|
|
|
|
|
|
|
## SQL优化步骤
|
|
|
|
|
## 优化步骤
|
|
|
|
|
**第1步:通过慢查日志等定位那些执行效率较低的SQL语句**
|
|
|
|
|
|
|
|
|
|
**第2步:explain分析SQL的执行计划**
|
|
|
|
|