1.TCP/IP连接

除了TCP/IP连接方式外,还有命名管道和共享内存,UNIX域套接字等方式,这里主要介绍TCP/IP
TCP/IP既可以适应客户端和服务端在同一个主机,又可以适应不在一个主机
而其他的方式只能是同一个主机

  1. Mysql使用TCP/IP作为服务器和客户端之间的网络通信协议,因为实际情况下二者是在不同的主机中。
  2. 二者在各自的主机上都是一个进程,每个主机有唯一的IP地址。
  3. 进程如果需要采用TCP/IP进行网络通信,就会向操作系统申请端口号,范围在0到65535,申请成功之后就可以使用IP地址和端口号与其他主机进程或者本机进程进行网络通信。(进程如果不进行网络连接的话就不会申请端口号)

2.服务器处理客户端请求

  1. 客户端向服务端传送一段文本(即Mysql语句,即增删改查等),服务端处理后返回给客户端一段文本(处理结果)。这种架构叫做客户端/服务端架构。
  2. 客户端可以向服务端发送增删改查等各类请求,下面以比较复杂的查询为例展示具体过程。
  3. 服务器处理查询请求会分为三个部分:连接管理,解析与优化 ,存储引擎。
  4. 连接管理:当使用TCP/IP连接或者命名管道等方式进行客户端和服务端的连接之后,服务器进程会为每一个连接请求创建一个线程用来交互,当客户端退出后会与服务器断开连接,但是创建完的线程不会被销毁,而是会缓存起来,当有新的连接创建时就是将该线程分配给新的连接,这样的话节省开销。从这里可以看出,服务器会为每一个连接进来的客户端分配一个线程,这就会产生问题,当有很多恶意请求访问数据库,数据库一直创建线程就会不断销毁内存,直到数据库崩溃。或者说正常访问的用户太多了,我们需要限制可以同时连接到服务器的客户端数量。一个可行的解决方法是设置过滤器,对于恶意请求进行过滤。比如客户端程序发起连接时需要携带主机信息,用户名,密码等信息,服务器程序会这些信息进行认证,如果认证失败,服务器会拒绝连接。
  5. 解析与优化:服务器从客户端获得了文本形式的请求,需要经过查询缓存,语法解析,查询优化三个步骤。
  6. 查询缓存:mysql会将刚刚处理过的查询请求和结果缓存起来,如果下次有同样的请求过来,直接从缓存中查找结果就行。同样的请求要求字符上要完全相同,包括空格,注释,大小写等,但是也有特殊情况,比如调用系统函数,如now函数返回当前时间,此时就不能用缓存。同时mysql缓存也会监控缓存区中涉及到的每张表,如果哪张表发生了变化,缓存中与该表相关的缓存都会被删除。(mysql 8.0中将查询缓存删除了,因为查询缓存占空间,而且需要维护。)
  7. 语法解析:将客户端发送过来的文本进行解析,具体就是将文本中要查询的表和各种查询条件都提取出来放到mysql服务器内部使用的一些数据结构上。
  8. 查询优化:语法解析之后,获得了需要查询的表和列,以及查询条件。mysql的优化程序会对我们的语句做一些优化,如外连接转换为内连接,表达式简化,子查询转为连接等一堆东西。优化的结果就是生成一个执行计划,执行计划表明了应该使用哪些索引进行查询,以及表之间的连接顺序是啥样的等等。
  9. 存储引擎:mysql服务器将数据的存储和提取操作封装到了一个名为存储引擎的模块中。我们常见的数据表示是由一行一行的记录组成的,但这只是一个逻辑上的概念。实际上数据是存储在物理磁盘中的,如何把数据写到磁盘中,如何从磁盘中读取数据都是存储引擎负责的事情。(存储引擎早期叫做表处理器,这样太土)
  10. 为了方便管理,人们把mysql服务器出来请求的过程简单划分为server层和存储引擎层。server层即连接管理,查询优化这两个部分,而存储引擎由于涉及真实数据存取,故自立门户。其实我们知道存储引擎有很多种,比如innodb,myisam等,那么为了兼容性,每个存储引擎都会为server层提供统一的调用接口,即一个server层可以在任意存储引擎之间进行切换使用。这样server层就不用管具体的存储引擎是什么样的,它只管调用统一的接口就行。接口中包含几十个不同用户的底层函数,比如“读取索引第一条记录”,“读取索引下一条记录”,“插入记录”等。
  11. 在server层完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的接口获取到数据后返回给客户端即可。server层和存储引擎层交互时,一般是以记录为单位的。以select语句为例,server层根据执行计划先向存储引擎层取一条记录,判断是否符合where条件,如果符合,就发送到缓冲区,如果不符合,就跳过该记录,继续向存储引擎索要下一条记录,以此类推(这是全表扫描吗?)。只有当缓冲区满了,才会向客户端发送真正的记录。
  12. mysql5.5.5开始Innodb称为mysql的默认存储引擎,之前是myisam
  13. innodb和myisam的区别是:(转转二面问到,转转二面都是泪啊)

3.字符集和比较规则

  1. 计算机中的是二进制数据,要想存储字符串,需要进行字符和二进制数据的映射关系。
  2. 界定字符范围:即哪些字符映射成二进制数据
  3. 映射规则:将字符映射为二进制数据叫做编码,反过来就是解码。
  4. 字符集:描述某个字符范围内的编码规则
  5. 比较规则:可以直接比较二进制的大小,也可以将大小写不同的字符全部转为大写或小写之后再比较这两个字符对应的二进制数据,还有其他更复杂的比较规则。
  6. 常见的字符集有ASCII,GBK,UTF-8等。
  7. mysql中utf8mb3表示阉割过的utf-8字符集,使用1~3个字节表示字符。
  8. mysql中utf8mb4表示正宗的utf-8字符集,使用1~4个字节表示字符。
  9. mysql中utf8是utf8mb3的别名。
  10. mysql有4个级别的字符集和比较规则。分别是服务器级别,数据库级别,表级别,列级别。
  11. 不用说也知道范围小的能覆盖范围大的,范围小的如果没设置就会使用上一个范围的。
  12. 字符集的关键字是charset,比较规则的关键字是collate。

4.客户端和服务器通信过程中使用的字符集

  1. 编码和解码使用的字符集不一致。
  2. 比如说一个字符串A经过utf-8编码成二进制数据,如果该字节序列发送到了其他主机,而该主机上的字符集是GBK,那么解码的时候就肯定会出错。
  3. 这让我想到小滴商城中拷贝过来的sql文件中的有些汉字是乱码或者?的形式,是不是sql语句本质是二进制的形式,(任何文件本质都是二进制形式啊)我在navicat或者sqlyog中打开就会进行解码展示?sql文件本身可以用记事本打开。展示出来的就是sql语句。但是用记事本打开的话本来就需要设置编码格式。采用不同的编码格式展示的内容可能不一样。
  4. 如果主机1接收到主机2发送给它的用utf-8编码的字节序列,然后用utf-8进行解码转换为字符串之后,又以gbk的格式将该字符串进行了编码,这种过程就叫做字符集转换。
  5. 我们把mysql客户端和服务器进行通信的过程中事先规定好的数据格式称为mysql通信协议。
  6. 客户端发送请求:如果没有在启动mysql客户端的时候进行显式设置,那么客户端编码请求字符串的时候使用的字符集与操作系统当前使用的字符集一致。
  7. 服务器接收请求:服务器会将接收到的请求看成是使用系统变量character_set_client代表的字符集进行编码的字节序列(每个客户端与服务器建立连接后,服务器都会为该客户端维护一个单独的character_set_client变量),这个变量是Session级别的。session对应的是单个连接,而global则是服务器级别。如果客户端使用的字符集编码后的字节序列,服务器方无法解码就会报错。
  8. 服务器处理请求:。。。看不下去了。
  9. 服务器生成响应:
  10. 客户端接收响应:

5.从一条记录说起—Innodb记录的存储结构

  1. 从标题2中我们知道mysql服务器中负责对表中的数据进行读取和写入工作的部分是存储引擎。
  2. 不同的存储引擎中,真实数据的存放格式是不同的,甚至有的存储引擎如memory不用磁盘来存储数据,对于使用memory引擎的表来说,关闭服务器后表中的数据就消失了。这句话有一个点:不同的表可以使用不同的存储引擎啊。
  3. Innodb引擎中,真实处理数据的过程发生在内存中,所以需要把磁盘中的数据加载到内存中。如果是处理写入或修改请求,还需要把内存中的内容刷新到磁盘上。我的问题是怎么加载?全部加载?怎么部分加载?需要把哪些数据加载到内存中?
  4. 我觉得应该是这样的,先把对应表的根节点对应的页面加载到内存中,再在内存中根据二分查找确认下一个要查找的页号,再从磁盘中加载该页号到内存中。
  5. Innodb将数据分为若干个页,每个页的大小一般为16kb,以页作为磁盘和内存之间交互的基本单位。也就是说,在一般情况下,一次最少从磁盘中读取16kb内容到内存中,一次最少把内存中的16kb内容刷新到磁盘中。(16kb的内容是怎么组合起来的??)
  6. 我们平时都是以记录为单位向表中插入数据的。这些记录在磁盘上的存放形式也被称为行格式或者记录格式。mysql中有很多行格式,这里重点讲COMPACT行格式。
  7. COMPACT行格式在磁盘中分成两大块,分别是记录的额外信息和记录的真实信息。
  8. 记录的额外信息有:变长字段长度列表,null值列表,记录头信息。
  9. 记录的真实数据有:我们人为插入的字段,mysql默认添加的一些隐藏字段。
  10. 隐藏字段有row_id,trx_id,roll_pointer,这三个都很重要
  11. 比如说row_id,当用户没有定义主键,也没有定义not null的unique键,那么mysql默认添加row_id的隐藏列作为主键,所以说mysql中是一定有主键的。如果用户给该表设置了主键或者not null的unique键,那么row_id将不复存在。trx_id是事务id,在事务那块很重要。
  12. 即我们插入一条数据,就会在磁盘中生成上面的两大块数据。这两块数据应该是连续的。
  13. 这两块数据是贯穿mysql始终的东西,需要着重了解。
  14. 变长字段长度列表:存放的是varchar类型的字段占用的真实字节数;
  15. null值列表:对应可以为null的字段而言,如果该字段真实存储的值为空,我们就会将null值列表的对应bit设置为1,比如有三个可以为null的字段真实值为null,那么null值列表就会是0000 0111,高位的零是因为null值列表规定占用的空间必须是整数个字节,不够的话就需要补零。注意这些位置是为哪些可以为null的字段提前准备的,not null的字段不会在null值列表中有位置。之所以使用null值列表是因为如果把null值存储在记录的真实信息那块存储区域会很占地方。
  16. 记录头信息是记录的额外信息中最重要的部分。记录头占用5个字节,也就是40个bit。这里先不讲。
  17. 溢出列:如果表中有一个列有一个字段是c varchar(65532),如果我们在实际插入记录的时候插入了一个字符长度为65532的字符串,如把’a’重复65532次,那么由于一个页只能存储16kb的大小,也就是16384字节,所以明显存放不下去,此时会在记录的真实数据处存储该字段的一部分数据,把剩余的数据分散存储在几个其他的页中,然后在记录的真实数据处用20字节存储指向这些页的地址。这些地址就叫溢出列地址。我们把该字段叫做溢出列。

6.盛放记录的大盒子—-innodb数据页结构

  1. 页是Innodb管理存储空间的基本单位,一个页的大小是16kb。Innodb中有很多种类型的页。比如存放表空间信息的页,存放undo日志信息的页等等。我们现在关心的是存放表中记录的那种类型的页,官方称这种存放记录的页为索引页,注意索引页不是索引,两码子事。本书将索引页看成数据页。
  2. 注意标题5中讲的是记录的存储结构,本标题就是讲记录是怎么存放在数据页中的。
  3. 一个数据页16kb,它可以划分为多个部分,不同部分有不同的功能。有些部分占用的字节数是确定的,有些不确定。
  4. 用户记录存放在user records中,一开始并没有user records区域,每当插入一条记录,就会从free space中申请一个记录大小的空间,并将这个空间划分到user records区域。如果一直插入记录,当free space空间的内存都被user records申请完之后就只能取申请新的数据页。
  5. 首先要来讲前面没讲的记录额外信息中的记录头信息。
  6. 设计Innodb的大叔把记录一条一条亲密无间排列的结构称之为堆。
  7. heap_no属性指的是本条记录在堆中的相对位置,比如插入的第一条数据的heap_no是2,第二条是3,之后以此类推,那么heap_no等于0和1的记录呢?为0的记录叫做页面中的最小记录,也叫Infimum记录,为1的记录叫做页面中的最大记录,也叫Supremum记录。记录还可以比大小??
  8. 对于一条完整的记录来说,记录通过比较主键的大小来比大小。对于只存储部分列的情况需要另说。
  9. 不论我们向本页中插入多少条记录,Innodb规定,任何用户记录都比infimum记录大,比supremum记录小。
  10. infimum和supremum这两条记录比较特殊,没有主键值,同时也不是存放在user records区域中。这是innodb规定的,他两存放的区域就在user records上方。
  11. 由于被删除的记录还会存在在页中,所以对于heap_no值而言,堆中记录的heap_no值在分配之后就不会发生改动了,即使之后删除了堆中的某条记录,这条被删除记录的heap_no值也仍然保持不变。
  12. record_type属性表示当前记录的类型。0表示普通记录,1表示B+树非叶节点的目录项记录,2表示Infimum记录,3表示Supremum记录。我们自己插入的记录就是普通记录,而1的情况在讲索引的时候再说。
  13. next_record表示当前记录的真实数据(记录分为额外信息和真实数据)到下一条记录的真实数据的距离。如果该属性为正数,表示当前记录的下一条记录在当前记录的后面,如果该属性值为负数,说明当前记录的下一条记录在当前记录的前面。比如第1条记录的next_record值为32,表示从第1条记录的真实数据的地址向后找32字节便是下一条记录的真实数据。再比如,第4条记录的next_record值为-111,表示从第四条记录的真实数据的地址处往前找111字节便是下一条记录的真实数据。
  14. 需要注意的是下一条记录不是插入顺序的下一条记录(插入的顺序应该是指heap_no属性),而是按照主键值从小到大的顺序排列的下一条记录。我们之前说过尽管infimum和supremum中没有主键,但是innodb规定前者是当前页中的最小记录,后者是最大记录。当我们在该页中插入4条数据时,第4条记录的next_record值为-111是因为它指向了supremum记录,而这条记录是堆中的第二条记录(从相对位置来说),而第四条记录是堆中的第6条记录,所以supremum在第四条记录的前面,所以next_record值为负数。
  15. 由记录的大小比较可知,一个数据页中的各个记录连成了一个单向链表,从infimum开始到supremum结束。无论对数据页中的记录进行各种增删改,INNODB始终会维护记录的一个单向链表,链表中的各个节点是按照主键值从小到大的顺序连接起来的。
  16. 我们现在知道了记录是单向链表中的节点,那么我们想找到某一条记录是不是需要从头结点infimum开始遍历查找呢?其实不是这样的,因为这个单链表不是普通的单链表,每个节点的对应的记录中的主键值是从小到大排列的,所以我们立马就可以想到用二分查找。
  17. 将所有正常的记录划分为几个组(包括infimum和supremum,但不包括已经移除到垃圾链表的记录);
  18. 每个组的最后一条记录(也就是组内最大的那条记录)相当于带头大哥,组内其余的记录相当于小弟。带头大哥这条记录的头信息中的n_owned属性表示该组内共有几条记录(包括带头大哥自身)。
  19. 将每个组的带头大哥在页面中地址偏移量(就是该记录的真实数据与页面中第0个字节之间的距离)单独提取出来,按顺序存储到靠近页尾部的地方。这个地方就是page Directory(页目录),这个也是数据页中的区域之一,页目录中的这些地址偏移量称为槽(slot),每个槽占用2字节。页目录就是由多个槽组成的。
  20. 槽就是地址,而一个页面也就16kb,即16384字节,所以两个字节就可以将地址0到地址16384全部表示出来。
  21. 为什么要使用槽呢?我们在二分查找的时候是mid=(start+end)/2,每次都得自己求mid,而我认为这里的槽就是提前算好很多中间的位置,然后根据这些槽来求二分,相当于将细粒度的mid转换为粗粒度的槽,锁定到某个槽后然后在槽对应的组中进行遍历得到最终的记录。所以说这个还不是纯粹的二分。
  22. 比如现在有槽0到槽4共5个槽,我们想查询主键值为6的记录,
  23. 初始时刻low=0,high=4;
  24. 计算中间槽的位置:(0+4)/2=2,查看槽2对应记录的主键值为8,而8大于6,high变为2,low保持不变;
  25. 故重新计算中间槽的位置:(0+2)/2=1,查看槽1对应的记录的主键值为4,而4小于6,low变为1,high保持不变;
  26. 此时high-low=1,所以可以确定主键值为6的记录在槽2对应的组内,所以需要找到槽2所在分组中主键值最小的那条记录,然后沿着单向链表遍历该分组中的记录。我们可以从槽1对应的记录入手,它的下一条记录就是槽2对应分组的主键值最小的那条记录。(因为一个数据页的记录链表是一条主键值不断增大的单向链表)
  27. 综上所述:在一个数据页中查找指定主键值的记录时,过程可以分为两步:
  28. 1.通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录。
  29. 2.通过记录的next_record属性遍历该槽所在组中的各个记录(其实就是单向链表遍历,单向链表的底层就是next_record属性)
  30. FileHeader作为数据页的第一个区域,它不仅适用于数据页,也适用于其他类型的页。它用来存储当前页的页号,以及当前页的前一页的页号,当前页的下一页的页号。这样通过建立一个双向链表就把许许多多的页串联起来。对了,只有数据页有双向链表。其他的页没有双向链表,但是有页号。
  31. FileTrailer作为数据页的最后一个区域,主要用于刷新校验。

7.快速查询的秘籍—-B+树索引

  1. 从前面的知识可以知道,各个数据页之间可以组成一个双向链表,而每个数据页内部的记录会按照主键值从小到大的顺序组成一个单向链表。每个数据页都会为存储在它里面的记录生成一个页目录,在通过主键查找某条记录的时候时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。(这个是必须要掌握的)
  2. 上面其实只解决了在单个数据页中的快速查找,如果需要在很多页中进行查找,那我们如何定位到记录所在的页。只能是从第一页开始沿着双向链表一直往下找,但是这样效率太低了。
  3. 索引横空出世。
  4. 直接说INNODB中的索引方案吧。
  5. 首先讲目录项记录,它的真实数据包括页号和页号对应的数据页中的最小主键值。
  6. 我们将若干个目录项记录也以单向链表的形式存储起来,放在一个数据页中,这个就跟普通的用户记录在数据页中的存储方式很像了,每个目录项记录中的record_type属性值为1,表示这条记录是目录项记录。每个用户记录中的record_type属性值为0,表示这条记录是用户记录。总结:专门构建一个数据页来存放目录项记录, 目录项记录只要页号和页号对应的数据页中的最小主键值这两个列,而普通用户记录的列是用户自己定义的,可能包含很多列,另外还有Innodb自己添加的隐藏列。目录项记录也是记录,也会有记录的额外信息,页号和主键这两个列在记录的真实数据中。比如目录项的记录头信息中的min_rec_flag表示B+树每层非叶子节点中的最小的目录项记录都会添加该标记。
  7. 目录项记录和用户记录都是放在数据页中,只不过目录项记录的列只有页号和主键,而用户记录的列是用户自定义的。
  8. 一个页能存放的目录项记录也是有限的,因为一个页就16kb啊,当目录项记录很多时,就得多创建几个数据页来存放目录项记录。那问题又来了,怎么从这么多存放目录项记录的数据页中快速找到我们想要的数据页呢?解决方法就是再为这些存储目录项记录的数据页再生成一个更高级的目录,这就是B+树的结构。也就是说非叶子节点存储的都是目录项记录,而只有叶子节点才是存储用户记录。
  9. 树是倒过来的树,上面是树根,下面是树叶。
  10. 假设所有存放用户记录的叶子节点所代表的数据页可以存放100条用户记录,所有存放目录项记录的非叶子节点可以存放1000条目录项记录。那么:
  11. 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,则最多能存放100条用户记录;
  12. 如果有2层,则最多存放1000*100=100,000条用户记录,因为是2层,所以根节点只有一个数据页用来存储目录项记录,最多是1000条目录项,然后每个目录项指向一个用户存放用户记录的数据页,每个数据页可以存放100条用户记录,那么结果不就很明显了嘛。
  13. 如果有3层,则最多存放10001000100=100,000,000条用户记录,分析方法按上面的思路即可。
  14. 如果有4层,则最多存放1000亿条记录,这太多了。
  15. 一般B+树不会超过4层,在通过主键值去查找某条记录时,最多只需要进行4个页面内的查找,即查找3个存储目录项记录的页和1个存储用户记录的页。又因为在每个页面中存在page Directory页目录(存储目录项记录的页也有页目录(就是存放槽的区域)),所以在页面内可以通过二分法快速定位记录。
  16. 那面试官问你,b+树是什么,就是目录项记录对应的数据页和用户记录对应的数据页构成的一颗树。
  17. 聚簇索引:两个特点:(为主键索引构建的B+树就是聚簇索引)
  18. 各个页内按照主键值从小到大排成一个单向链表,叶子节点对应的页之间按照主键值从小到大排成双向链表,非叶子节点对应的页也是按照主键值从小到大排成双向链表。
  19. 叶子节点存储的是完整的用户记录。所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
  20. 只要不是聚簇索引的索引不就是非聚簇索引嘛。
  21. 二级索引:聚簇索引只能在搜索条件为主键值时才能发挥作用。那如果我们在where子句中想以别的列作为搜索条件怎么办?解决途径是我们为其他的非主键索引建立相应的B+树。这些B+树的非叶子节点中存储该列值,主键值,页号,而叶子节点中存储该列值,主键值。(由于二级索引对应的列(unique除外)往往可以重复,所以当重复时我们就需要利用主键来排序)我们根据列值找到主键值之后需要进行回表操作,即根据主键值到聚簇索引再进行查找。注意由于其他的索引并不是唯一的,所以当我们在二级索引中的叶子节点找到一个符合条件的节点之后就做一次回表,然后回表完成之后又得继续在二级索引的叶子节点处向右移动,每次找到一个符合条件的节点都得回表,直到下一个节点不符合条件就可以退出了。
  22. 联合索引:我们同时为多个列建立索引。比如我们想让B+树按照c2和c3列的大小进行排序,意思是:先把记录和页按照c2列进行排序,在记录的c2列相同的情况下,再采用c3列进行排序。非叶子节点的目录项记录中存储c2列,c3列,主键值,页号,而叶子节点的用户记录中存储c2列,c3列,主键值。联合索引本质也是二级索引 。
  23. 之所以二级索引中的非叶子节点的目录项记录要存放主键值,是因为当插入一条数据的时候,如果没有主键值来辅助判定应该插入到哪个页中,当当前索引的值有相同的值(因为当前索引的值是可以重复的)时是无法判定插入到哪个页中的。
  24. 一个页面为什么至少容纳2条记录。因为1的n次方还是1.
  25. myisam中的索引方案:用户记录专门存放在一个文件中,每条记录对应一个行号。主键对应的索引不是聚簇索引,而是非聚簇索引,该索引的叶子节点中存储的不是完整的用户记录,而是主键值加行号。即myisam中是通过主键值找到行号,再由行号找到用户记录。

8.B+树索引的使用

  1. 每个索引都对应一颗B+树。B+树分为好多层,最下边一层是叶子节点,其余的是非叶子节点。所有的用户记录都存储在B+树的叶子节点,所有的目录项记录都存储在非叶子节点。
  2. Innodb存储引擎会自动为主键建立聚簇索引,这个主键要么是人为设置的,要么没有人为设置的情况下就会添加隐藏主键,聚簇索引的叶子节点包含完整的用户记录。
  3. 我们可以为感兴趣的列建立二级索引。二级索引的叶子节点包含的用户记录由索引列和主键组成。如果想通过二级索引查找完整的用户记录,需要执行回表操作。(如果不需要查找完整的用户记录,比如实际查找的就只有索引列和主键,那么就直接找到了,就不需要进行回表操作)。回表操作就是通过二级索引找到主键值之后,再到聚簇索引中查找完整的用户记录。
  4. B+树的每层节点都按照索引列的值从小到大的顺序组成了双向链表,而且每个页内的记录(无论是用户记录还是目录项记录)都按照索引列的值从小到大的顺序形成了一个单向链表。如果是联合索引,则页面和记录先按照索引列前面的列的值排序。如果该列的值相同,再按照索引列后面的值排序。
  5. 通过索引查找记录时,是从B+树的根节点开始一层一层向下搜索的。由于每个页面(无论是叶子节点还是非叶子节点)中的记录都划分成了若干个组,每个组中索引列值最大的记录在页内的偏移量会被当做槽依次存放在页目录中(当然,规定supremum记录比任何用户记录都大),因此可以在页目录(由很多的槽组成)中通过二分法快速定位到索引列等于某个值的记录。
  6. 这上面的总结不懂,那你就再打打基础吧。
  7. 索引的代价:空间上代价:每建立一个索引都要建立一颗B+树,而树的节点都是一个数据页。一个数据页会占用16kb的内存空间,一棵树的所有数据页会占用很大的空间。时间上的代价:每次进行增删改(没有查)操作时,都需要修改维护各个B+树索引。因为需要维持从小到大的顺序。
  8. 为什么页与页之间用双向链表(降序排序算不算?),而页内部的记录之间用单向链表就行?
  9. 我猜还可能是原因是页内部有槽(每个组的老大到堆头的偏移地址),而页之间是没有槽的,为什么不弄槽的,因为页号是没有规律的,不像页内部的槽是从小到大的顺序。有槽的话可以从后面跳到前面,没有槽的话就只能用双向链表。(单向链表往前跳的原因是槽对应的是该组内的最大地址,而实际情况是要从最小地址开始扫描直到扫描到符合的记录)
  10. 什么时候磁盘中的数据会加载到内存中去?增删改查都是在内存中进行的吗?需要加载多少数据到内存中?
  11. 全表扫描:意味着从聚簇索引第一个叶子节点的第一条记录开始,沿着记录所在的单向链表向后扫描,直到最后一个叶子节点的最后一条记录。
  12. 比如我们想从聚簇索引中查询主键为i的记录,肯定是从根节点开始,根节点会有若干个主键值,我们当然要使用二分法快速定位主键i应该走向哪个主键值对应的下一个页中。
  13. 如果where子句有多个二级索引对应字段,那只会走其中一个二级索引吗??发出这个疑问是因为这个章节在讨论扫描区间的时候,都在说假设使用这个二级索引进行查询,假设使用哪个二级索引进行查询。
  14. 使用联合索引进行纪录查找是否相当于多级排序方式下的二分查找,有“多级排序方式下的二分查找”这个东西吗?
  15. 联合索引中是如何定位到满足条件的第一条记录的?(看书上p99页的查找过程)就是一个查找可能在叶子节点会对应很多的数据页,需要从所有可能的数据页中从最左边开始扫描,扫描到的第一个就是满足条件的第一条记录。
  16. 索引下推:比如我们以a,b,c建立一个联合索引,查询语句为select * from 表名 where a=1 and c=2;这个时候只会走a不会走c,因为a=1的记录中并不是按照c排序的,此时会定位到符合a=1的第一条记录,然后沿着记录所在的单向链表向后扫描,每扫描一个的话就得判断c是否等于2,等于2就回表,不等于就继续向后扫描知道扫描到a不等于1的记录。这里我们利用c是否等于2可以减少回表次数,这种叫做索引下推,这应该只在联合索引中有。

9.最左匹配原则(这是联合索引的知识)

细看
image.png

为什么强调有序,
因为索引借助二分查找发挥优势,
二分查找就得有序,否则无法进行二分查找。
这样就可以解释最左匹配原则,比如c>5的时候,d是无序的,所以不会走联合索引。
但是如果建立的不是abcd的联合索引,而是abdc的联合索引,那么此时d=6时,c>5的范围内c是有序的,所以可以走联合索引。

SELECT ‘a’ = ‘A’这个语句怎么理解??