diff --git a/database/mysql/README.md b/database/mysql/README.md index 6bf067a..4b4556e 100644 --- a/database/mysql/README.md +++ b/database/mysql/README.md @@ -343,14 +343,123 @@ - 这是因为有时候会出现多个索引页指向的下层页号的最小name字段值是一样的,此时就必须根据主键判断一下。 - 新的name字段值肯定是插入到主键值较大的那个数据页里去的。 - - - - - - - - +## 72. 一个表里是不是索引搞的越多越好?那你就大错特错了! +- 正常我们在一个表里灌入数据的时候,都会基于主键给我们自动建立聚簇索引 +- 随着我们不停的在表里插入数据,他就会不停的在数据页里插入数据,然后一个数据页放满了就会分裂成多个数据页,这个时候就需要索引页去指向各个数据页 +- 如果数据页太多了,那么索引页里里的数据页指针也就会太多了,索引页也必然会放满的,此时索引页也会分裂成多个,再形成更上层的索引页 +- 默认情况下MySQL给我们建立的聚簇索引都是基于主键的值来组织索引的,聚簇索引的叶子节点都是数据页,里面放的就是我们插入的一行一行的完整的数据了 +- 在一个索引B+树中,他有一些特性 + - 数据页/索引页里面的记录都是组成一个单向链表的而且是按照数据大小有序排列的 + - 数据页/索引页互相之间都是组成双向链表的,而且也都是按照数据大小有序排列的 + - 其实B+树索引是一个完全有序的数据结构,无论是页内还是页之间 + - 因为这个有序的B+树索引结构,才能让我们查找数据的时候,直接从根节点开始按照数据值大小一层一层往下找,这个效率是非常高的。 +- 如果是针对主键之外的字段建立索引的话,实际上本质就是为那个字段的值重新建立另外一颗B+树索引 + - 那个索引B+树的叶子节点,存放的都是数据页,里面放的都是你字段的值和主键值,然后每一层索引页里存放的都是下层页的引用 + - 包括页内的排序规则,页之间的排序规则,B+树索引的搜索规则,都是一样的 +- 假设我们要根据其他字段的索引来搜索,那么只能基于其他字段的索引B+树快速查找到那个值所对应的主键,接着再次做回表查询 +- 基于主键在聚簇索引的B+树里,重新从根节点开始查找那个主键值,找到主键值对应的完整数据 +- 你在MySQL的表里建立一些字段对应的索引,好处是什么? + - 你在MySQL的表里建立一些字段对应的索引,好处是什么? + - 但是坏处呢?索引当然有缺点了,主要是两个缺点,一个是空间上的,一个是时间上的。 + - 空间上而言,你要是给很多字段创建很多的索引,那你必须会有很多棵索引B+树,每一棵B+树都要占用很多的磁盘空间啊!所以你要是搞的索引太多了,是很耗费磁盘空间的。 + - 你要是搞了很多索引,那么你在进行增删改查的时候,每次都需要维护各个索引的数据有序性,因为每个索引B+树都要求页内是按照值大小排序的,页之间也是有序的,下一个页的所有值必须大于上一个页的所有值! + - 所以你不停的增删改查,必然会导致各个数据页之间的值大小可能会没有顺序,比如下一个数据页里插入了一个比较小的值,居然比上一个数据页的值要小!此时就没办法了,只能进行数据页的挪动,维护 + 页之间的顺序。 + - 或者是你不停的插入数据,各个索引的数据页就要不停的分裂,不停的增加新的索引页,这个过程都是耗费时间的。 + - 所以你要是一个表里搞的索引太多了,很可能就会导致你的增删改的速度就比较差了,也许查询速度确实是可以提高,但是增删改就会受到影响,因此通常来说,我们是不建议一个表里搞的索引太多的! +- 那么怎么才能尽量用最少的索引满足最多的查询请求,还不至于让索引占用太多磁盘空间,影响增删改性能呢?这就需要我们深入理解索引的使用规则了,我们的SQL语句要怎么写,才能用上索引B+树来查询! + +## 73. 通过一步一图来深入理解联合索引查询原理以及全值匹配规则 +- 之所以讲解联合索引,那是因为平时我们设计系统的时候一般都是设计联合索引,很少用单个字段做索引,原因之前讲过,我们还是要尽可能的让索引数量少一些,避免磁盘占用太多,增删改性能太差。 +- 然后呢,就是包含了学生班级、学生姓名、科目名称、成绩分数四个字段,平时查询,可能比较多的就是查找某个班的某个学生的某个科目的成绩。 +- 所以,我们可以针对学生班级、学生姓名和科目名称建立一个联合索引。 +- 接着我们画了下面的一个图,这个图就展示了这个三个字段组成的联合索引的部分内容 +![联合索引](pic/联合索引.png) +- 下面有两个数据页,第一个数据页里有三条数据,每条数据都包含了联合索引的三个字段的值和主键值,数据页内部是按照顺序排序的。 +- 首先按照班级字段的值来排序,如果一样则按照学生姓名字段来排序,如果一样,则按照科目名称来排序,所以数据页内部都是按照三个字段的值来排序的,而且还组成了单向链表。 +- 然后数据页之间也是有顺序的,第二个数据页里的三个字段的值一定都大于上一个数据页里三个字段的值,比较方法也是按照班级名称、学生姓名、科目名称依次来比较的,数据页之间组成双向链表。 +- 索引页里就是两条数据,分别指向两个数据页,索引存放的是每个数据页里最小的那个数据的值,大家看到,索引页里指向两个数据页的索引项里都是存放了那个数据页里最小的值! +- 索引页内部的数据页是组成单向链表有序的,如果你有多个索引页,那么索引页之间也是有序的,组成了双向链表。 +- 假设我们想要搜索:1班+张小强+数学的成绩,此时你可能会写一个类似下面的SQL语句, + - select * from student_score where class_name='1班' and student_name='张小强' and subject_name='数学'。 + - 此时就涉及到了一个索引使用的规则,那就是你发起的SQL语句里,where条件里的几个字段都是基于等值来查询,都是用的等于号 + - 而且where条件里的几个字段的名称和顺序也跟你的联合索引一模一样!此时就是等值匹配规则 + - 上面的SQL语句是百分百可以用联合索引来查询的 + - 那么查询的过程也很简单了,首先到索引页里去找,索引页里有多个数据页的最小值记录,此时直接在索引页里基于二分查找法来找就可以了,先是根据班级名称来找1班这个值对应的数据页,直接可以定 + 位到他所在的数据页 + - 然后你就直接找到索引指向的那个数据页就可以了,在数据页内部本身也是一个单向链表,你也是直接就做二分查找就可以了,先按1班这个值来找, + 你会发现几条数据都是1班,此时就可以按照张小强这个姓名来二分查找,此时会发现多条数据都是张小强,接着就按照科目名称数学来二分查找。 + 很快就可以定位到下图中的一条数据,1班的张小强的数学科目,他对应的数据的id是127 + - 然后就根据主键id=127到聚簇索引里按照一样的思路,从索引根节点开始二分查找迅速定位下个层级的页,再不停的找,很快就可以找到id=127的那条数据,然后从里面提取所有字段,包括分数,就可以了。 + - 对于联合索引而言,就是依次按照各个字段来进行二分查找,先定位到第一个字段对应的值在哪个页里,然后如果第一个字段有多条数据值都一样,就根据第二个字段来找,以此类推,一定可以定位到某 + 条或者某几条数据! + +## 74. 再来看看几个最常见和最基本的索引使用规则 +- 当我们建立好一个联合索引之后,我们的SQL语句要怎么写,才能让他的查询使用到我们建立好的索引呢? +- 等值匹配规则,就是你where语句中的几个字段名称和联合索引的字段完全一样,而且都是基于等号的等值匹配,那百分百会用上我们的索引,这个大家是没有问题的,即使你where语句里 + 写的字段的顺序和联合索引里的字段顺序不一致,也没关系,MySQL会自动优化为按联合索引的字段顺序去找。 +- 现在看第二个规则,就是最左侧列匹配,这个意思就是假设我们联合索引是KEY(class_name, student_name, subject_name),那么不一定必须要在where语句里根据三个字段来查,其实只要根据 + 最左侧的部分字段来查,也是可以的。 + - 比如你可以写select * from student_score where class_name='' and student_name='',就查某个学生所有科目的成绩,这都是没有问题的。 + - 但是假设你写一个select * from student_score where subject_name='',那就不行了,因为联合索引的B+树里,是必须先按class_name查,再按student_name查,不能跳过前面两个字段,直接按最后一 + 个subject_name查的。 + - 另外,假设你写一个select * from student_score where class_name='' and subject_name='',那么只有class_name的值可以在索引里搜索,剩下的subject_name是没法在索引里找的,道理同上 + - 所以在建立索引的过程中,你必须考虑好**联合索引字段的顺序**,以及你平时写SQL的时候要按哪几个字段来查。 +- 第三个规则,是最左前缀匹配原则,即如果你要用like语法来查,比如select * from student_score where class_name like '1%',查找所有1打头的班级的分数,那么也是可以用到索引的。 + - 因为你的联合索引的B+树里,都是按照class_name排序的,所以你要是给出class_name的确定的最左前缀就是1,然后后面的给一个模糊匹配符号,那也是可以基于索引来查找的,这是没问题的。 + - 但是你如果写class_name like '%班',在左侧用一个模糊匹配符,那他就没法用索引了,因为不知道你最左前缀是什么,怎么去索引里找啊? +- 第四个规则,就是范围查找规则,这个意思就是说,我们可以用select * from student_score where class_name>'1班' and class_name<'5班'这样的语句来范围查找某几个班级的分数 + - 这个时候也是会用到索引的,因为我们的索引的最下层的数据页都是按顺序组成双向链表的,所以完全可以先找到'1班'对应的数据页,再找到'5班'对应的数据页,两个数据页中间的那些数据页,就全都是在你范围内的数据了! + - 但是如果你要是写select * from student_score where class_name>'1班' and class_name<'5班' and student_name>'', + 这里只有class_name是可以基于索引来找的,student_name的范围查询是没法用到索引的! + - 就是你的where语句里如果有范围查询,那只有对联合索引里最左侧的列进行范围查询才能用到索引! +- 第五个规则,就是等值匹配+范围匹配的规则,如果你要是用select * from student_score where class_name='1班' and student_name>'' and subject_name<'' + - 那么此时你首先可以用class_name在 索引里精准定位到一波数据,接着这波数据里的student_name都是按照顺序排列的,所以 student_name>''也会基于索引来查找,但是接下来的subject_name<''是不能用索引的 +- 总结: + - 一般我们如果写SQL语句,都是用联合索引的最左侧的多个字段来进行等值匹配+范围搜索 + - 或者是基于最左侧的部分字段来进行最左前缀模糊匹配 + - 或者基于最左侧字段来进行范围搜索 + +### 75. 当我们在SQL里进行排序的时候,如何才能使用索引? +- 当我们的SQL语句里使用order by语句进行排序的时候,如何才能用上索引呢? +- 假设你有一个select * from table where xxx=xxx order by xxx这样的一个SQL语句,似乎应该是基于where语句通过索引快速筛选出来一波数据,接着放到内存里,或者 + 放在一个临时磁盘文件里,然后通过排序算法按照某个字段走一个排序,最后把排序好的数据返回。 +- 假设你有一个select * from table where xxx=xxx order by xxx这样的一个SQL语句,似乎应该是基于where语句通过索引快速筛选出来一波数据,接着放到内存里,或者 + 放在一个临时磁盘文件里,然后通过排序算法按照某个字段走一个排序,最后把排序好的数据返回。 +- 通常而言,咱们尽量是最好别这么搞,尤其是类似于select * from table order by xx1,xx2,xx3 limit 100这样的SQL语句,按照多个字段进行排序然后返回排名前100条数据,类似的语句其实常常见于分页 + SQL语句里,可能需要对表里的数据进行一定的排序,然后走一个limit拿出来指定部分的数据 +- 你要是纯粹把一坨数据放到一个临时磁盘文件里,然后直接硬上各种排序算法在磁盘文件里搞一通排序,接着按照你指定的要求走limit语句拿到指定分页的数据,这简直会让SQL的速度慢到家了! +- 所以通常而言,在这种情况下,假设我们建立了一个INDEX(xx1,xx2,xx3)这样的一个联合索引,这个时候默认情况下在索引树里本身就是依次按照xx1,xx2,xx3三个字段的值去排序的,那么此时你再运行 + select * from table order by xx1,xx2,xx3 limit 100这样的SQL语句,你觉得还需要在什么临时磁盘文件里排序吗? +- 显然是不用了啊!因为他要求也不过就是按照xx1,xx2,xx3三个字段来进行排序罢了,在联合索引的索引树里都排序好了, + 直接就按照索引树里的顺序,把xx1,xx2,xx3三个字段按照从小到大的值获取前面 100条就可以了 +- 然后拿到100条数据的主键再去聚簇索引里回表查询剩余所有的字段。 +- 所以说,在你的SQL语句里,应该尽量最好是按照联合索引的字段顺序去进行order by排序,这样就可以直接利用联合索引树里的数据有序性,到索引树里直接按照字段值的顺序去获取你需要的数据了。 +- 但是这里有一些限定规则,因为联合索引里的字段值在索引树里都是从小到大依次排列的 ,所以你在 order by里要不然就是每个字段后面什么都不加,直接就是order by xx1,xx2,xx3,要不然就都加DESC + 降序排列,就是order by xx1 DESC,xx2 DESC,xx3 DESC +- 如果都是升序排列,直接就从索引树里最小的开始读取一定条数就可以了,要是都是降序排列,就是从索引树里最大的数据开始读取一定的条数就可以了,但是你不能order by语句里有的字段升序有的字段 + 降序,那是不能用索引的 +- 另外,要是你order by语句里有的字段不在联合索引里,或者是你对order by语句里的字段用了复杂的函数,这些也不能使用索引去进行排序了。 + +### 76. 当我们在SQL里进行分组的时候,如何才能使用索引? +- 有时候我们会想要做一个group by把数据分组接着用count sum之类的聚合函数做一个聚合统计。 +- 假设你要是走一个类似select count(*) from table group by xx的SQL语句,似乎看起来必须把你所有的数据放到一个临时磁盘文件里还有加上部分内存,去搞一个分组,按照指定字段的值分成一组一组 + 的,接着对每一组都执行一个聚合函数,这个性能也是极差的,因为毕竟涉及大量的磁盘交互。 +- 因为在我们的索引树里默认都是按照指定的一些字段都排序好的,其实字段值相同的数据都是在一起的,假设要是走索引去执行分组后再聚合,那性能一定是比临时磁盘文件去执行好多了 +- 所以通常而言,对于group by后的字段,最好也是按照联合索引里的最左侧的字段开始,按顺序排列开来,这样的话,其实就可以完美的运用上索引来直接提取一组一组的数据,然后针对每一组的数据执行聚合函数就可以了 +- 其实大家会发现,这个group by和order by用上索引的原理和条件都是差不多的,本质都是在group by和order by之后的字段顺序和联合索引中的从最左侧开始的字段顺序一致,然后就可以充分利用索引树 + 里已经完成排序的特性,快速的根据排序好的数据执行后续操作了。 +- 这样就不再需要针对杂乱无章的数据利用临时磁盘文件加上部分内存数据结构进行耗时耗力的现场排序和分组,那真是速度极慢,性能极差的。 +- 我们平时设计表里的索引的时候,必须充分考虑到后续你的SQL语句要怎么写,大概会根据哪些字段来进行where语句里的筛选和过滤?大概会根据 + 哪些字段来进行排序和分组? +- 在考虑好之后,就可以为表设计两三个常用的索引,覆盖常见的where筛选、order by排序和 group by分组的需求,保证常见的SQL语句都可以用上索引,这样你真正系统跑起来,起码是不会有太 + 大的查询性能问题了 +- 毕竟只要你所有的查询语句都可以利用索引来执行,那么速度和性能通常都不会太慢。如果查询还是有问题,那就要深度理解查询的执行计划和执行原理了,然后基于执行计划来进行深度SQL调优。 +- 然后对于更新语句而言,其实最核心的就是三大问题 + - 一个是你索引别太多,索引太多了,更新的时候维护很多索引树肯定是不行的; + - 一个是可能会涉及到一些锁等待和死锁的问题; + - 一个就是可能会涉及到 MySQL连接池、写redo log文件之类的问题 +- # 查询语句的执行原理 @@ -362,6 +471,7 @@ # SQL语句调优 +# 数据库的备份和恢复 @@ -371,13 +481,7 @@ - - - - - - -# 高可用及部署 +# 主从架构和读写分离,高可用架构,分库分表架构 diff --git a/database/mysql/pic/联合索引.png b/database/mysql/pic/联合索引.png new file mode 100644 index 0000000..c6c231a Binary files /dev/null and b/database/mysql/pic/联合索引.png differ