一、执行一条 select 语句,期间发生了什么?
(整体的结构,有哪些模块,做什么,要理解,后面需要能讲出来)
连接器:建立连接,管理连接、校验用户身份;
查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
执行 SQL:执行 SQL 共有三个阶段:
预处理阶段:检查表或字段是否存在;将 select *
中的 *
符号扩展为表上的所有列。
优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;
执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
二、MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?
(MySQL 存储引擎有哪些?Innodb 和 MyISAM 存储引擎有什么区别? )
存储方式 :MyISAM
引擎会将表数据和索引数据分成两个文件存储。
索引支持 :因为MyISAM
引擎的表数据和索引数据是分开的,因此不支持聚簇索引。
事务支持 :由于MyISAM
引擎没有undo-log
日志,所以不支持多条SQL
组成事务并回滚。
故障恢复 :MyISAM
引擎依靠bin-log
日志实现,bin-log
中未写入的数据会永久丢失。
锁粒度支持 :因为MyISAM
不支持聚簇索引,因此无法实现行锁,所有并发操作只能加表锁。
并发性能 :MyISAM
引擎仅支持表锁,所以多条线程出现读-写并发场景时会阻塞。
内存利用度 :MyISAM
引擎过于依赖MySQL Server
,对缓冲池、异步IO
技术开发度不够。
三、MySQL 一行记录是怎么存储的?
(数据库表的数据存放在哪?InnoDB 表空间结构有哪些组成?varchar 是怎么保存长度的?Null 值是如何保存的?)
1.MySQL 的数据都是保存在磁盘的( SHOW VARIABLES LIKE 'datadir';)
2.表空间由段(segment)、区(extent)、页(page)、行(row)组成
3.InnoDB 行格式有哪些?
Redundant(没人用)、Compact(主流)、Dynamic(和Compact类似)和 Compressed(和Compact类似)
4.COMPACT 行格式长什么样?
一条完整的记录分为「记录的额外信息」和「记录的真实数据」两个部分。
5.varchar 是怎么保存长度的?
存到「变长字段长度列表」里面,读取数据的时候根据这个「变长字段长度列表」去读取对应长度的数据。
6.Null 值是如何保存的?
MySQL 的 Compact 行格式中会用「NULL值列表」来标记值为 NULL 的列,NULL 值并不会存储在行格式中的真实数据部分。
NULL值列表会占用 1 字节空间,当表中所有字段都定义成 NOT NULL,行格式中就不会有 NULL值列表,这样可节省 1 字节的空间。
四、char和varcahr的区别
1.varchar和char在MySQL层的区别
varchar 是变长 的(Variable-length),char 是定长的(Fixed-length)。
最大长度:char是255,varchar是65535,单位是字符(而不是字节)。
尾随空格 :char会将尾随空格去掉,而varchar不会。 因为存储时,char会用空格填充至指定长度,所以取出时需要去除空格。如果char字段有唯一索引,a
和a
会提示唯一索引冲突。
存储空间占用:varchar会占用额外的1~2字节来存储字符串长度。如果最大长度超过255,就需要2字节,否则1字节。
2.varchar和char在存储引擎层的区别
varchar类型对于短字符串、长字符串、多字节编码,都是存储了实际的字符+字符长度。
char类型 的字段,innodb同样保存了字符长度(红色字体)。对于utf8mb4 char(50)
来说,长度不够50字节的字符串,会使用空格(0x20)填充到50个字节
3.char和varchar存储对比
char和varchar都会存储字符串长度
对于CHAR(N)
字段,如果实际存储数据小于N
字节,会填充空格到N
个字节。
4.性能对比
char填充空格可能导致浪费存储空间,进而导致性能下降。
极端场景 :某个字段的最大长度是100字节,但是会频繁修改。如果使用char(100)
,则插入记录后就分配了100个字节,后续修改不会造成页分裂、页空隙等问题,而varchar(100)
由于没有提前分配存储空间,后续修改时可能出现页分裂,进而导致性能下降。
5.char和varchar 使用场景
char 适应于固定长度的码值、定值;更新次数多的小长度字段;
varchar 适应于不定的字段,如审核意见、理由;更新次数少的字段;系统性能需要;
五、从数据页的角度看 B+ 树
(聚簇索引和非聚簇索引(二级索引)的 B+ 树结构有什么区别?)
InnoDB 的数据是按「数据页」为单位来读写的,默认数据页大小为 16 KB。每个数据页之间通过多个链表的形式组织起来,物理上不连续,但逻辑上连续。
数据页内包含用户记录,每个记录之间用单向链表的方式组织高效起来,为了加快在数据页内查询记录的速度,设计了一个页目录,页目录存储各个槽(分组),且主键值是分组的,那么可以通过二分查找法的方式进行检索从而提高效率。
为了高效查询记录每个所在的数据页,InnoDB采用b+树作为索引,节点都是一个数据页。
如果叶子节点存储的实际数据 的就是聚簇索引 ,一个表中只能有一个聚簇索引;如果叶子节点存储的不是实际数据,而是主键值 ,则就是二级索引,一个表中可以有多个二级索引。
在使用二级索引进行查找数据时,如果查询的数据能在二级索引找到,那么就是「索引覆盖」操作,如果查询的数据不在二级索引里,就需要先在二级索引找到主键值,需要去聚簇索引中获得数据行,这个过程就叫作「回表」。
六、揭开 Buffer Pool 的面纱
(InnoDB 对 LRU 做了哪些优化?)
1.Innodb 存储引擎设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。
2.Buffer Pool 以页为单位缓冲数据,可以通过 innodb_buffer_pool_size 参数调整缓冲池的大小,默认是 128 M。
3.Innodb 通过三种链表来管理缓页:
Free List (空闲页链表),管理空闲页;
Flush List (脏页链表),管理脏页;
LRU List,管理脏页+干净页,将最近且经常查询的数据缓存在其中,而不常查询的数据就淘汰出去。;
4.InnoDB 对 LRU 做了一些优化,我们熟悉的 LRU 算法通常是将最近查询的数据放到 LRU 链表的头部,而 InnoDB 做 2 点优化:
将 LRU 链表 分为young 和 old 两个区域,加入缓冲池的页,优先插入 old 区域;页被访问时,才进入 young 区域,目的是为了解决预读失效的问题。
当**「页被访问」且「 old 区域停留时间超过 innodb_old_blocks_time 阈值(默认为1秒)」**时,才会将页插入到 young 区域,否则还是插入到 old 区域,目的是为了解决批量数据访问,大量热数据淘汰的问题。
5.可以通过调整 innodb_old_blocks_pct 参数,设置 young 区域和 old 区域比例。
6.在开启了慢 SQL 监控后,如果你发现「偶尔」会出现一些用时稍长的 SQL,这可因为脏页在刷新到磁盘时导致数据库性能抖动。如果在很短的时间出现这种现象,就需要调大 Buffer Pool 空间或 redo log 日志的大小。
七、普通索引和唯一索引,应该怎么选择?
(普通索引和唯一索引有什么区别? 哪个更新性能更好?)
查询过程:性能差距微乎其微
普通索引,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足 k=5 条件的记录。
唯一索引,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。
更新过程:change buffer 减少了随机磁盘访问,更新性能提升明显。
唯一索引 的更新就不能使用 change buffer,实际上也只有普通索引可以使用。
索引选择和实践
这两类索引在查询能力上是没差别 的,主要考虑的是对更新性能 的影响。所以,建议尽量选择普通索引。
如果所有更新后面,都马上伴随着对这个记录的查询,那么应该关闭 change buffer。而在其他情况下,change buffer 都能提升更新性能。
八、索引:排序的艺术
(什么是索引?为什么索引能加快查询?索引的数据结构是什么?)
1.索引是什么?
索引是提升查询速度的一种数据结构 ,索引之所以能提升查询速度,在于它在插入时对数据进行了排序(它的缺点是影响插入或者更新的性能)InnoDB 存储引擎支持的索引有 B+ 树索引、全文索引、R 树索引
2.B+树索引结构
B+树索引结构是目前为止排序最有效率的数据结构
B+树索引的特点是: 基于磁盘的平衡树,但树非常矮,通常为 34 层,能存放千万到上亿的排序数据。树矮意味着访问效率高,从千万或上亿数据里查询一条数据,只用 3、4 次 I/O。又因为现在的固态硬盘每秒能执行至少 10000 次 I/O ,所以查询一条数据,哪怕全部在磁盘上,也只需要 0.003 ~ 0.004 秒。另外,因为 B+ 树矮,在做排序时,也只需要比较 34 次就能定位数据需要插入的位置,排序效率非常不错。
B+ 树索引由根节点(root node)、中间节点(non leaf node)、叶子节点(leaf node)组成 ,其中叶子节点存放所有排序后的数据。 当然也存在一种比较特殊的情况,比如高度为 1 的B+ 树索引
九、为什么 MySQL 采用 B+ 树作为索引
(B+ 树和(B 树和红黑树)有什么区别?为什么选择用 B+ 树 作为索引数据结构?)
1.怎样的索引的数据结构是好的?
能在尽可能少的磁盘的 I/O 操作中完成查询工作;
要能高效地查询某一个记录,也要能高效地执行范围查找;
2.什么是二分查找?
假设我们现在用数组来存储索引,比如下面有一个排序的数组,如果要从中找出数字 3,最简单办法就是从头依次遍历查询,这种方法的时间复杂度是 O(n),查询效率并不高。因为该数组是有序的,所以我们可以采用二分查找法,比如下面这张采用二分法的查询过程图:
可以看到,二分查找法每次都把查询的范围减半,这样时间复杂度就降到了 O(logn),但是每次查找都需要不断计算中间位置。
3.什么是二分查找树(不适合做索引结构)
用数组来实现线性排序的数据虽然简单好用,但是插入新元素的时候性能太低。
因为插入一个元素,需要将这个元素之后的所有元素后移一位,如果这个操作发生在磁盘中呢?这必然是灾难性的。因为磁盘的速度比内存慢几十万倍,所以我们不能用一种线性结构将磁盘排序。
其次,有序的数组在使用二分查找的时候,每次查找都要不断计算中间的位置。
二叉查找树: 找到所有二分查找中用到的所有中间节点 ,把他们用指针连起来,并将最中间的节点作为根节点。
二叉查找树的特点是一个节点的左子树的所有节点都小于这个节点,右子树的所有节点都大于这个节点,这样我们在查询数据时,不需要计算中间节点的位置了,只需将查找的数据与节点的数据进行比较。
假设,我们查找索引值为 key 的节点:
如果 key 大于根节点,则在右子树中进行查找;
如果 key 小于根节点,则在左子树中进行查找;
如果 key 等于根节点,也就是找到了这个节点,返回根节点即可。
二叉查找树解决了插入新节点的问题 ,因为二叉查找树是一个跳跃结构,不必连续排列。这样在插入的时候,新节点可以放在任何位置,不会像线性结构那样插入一个元素,所有元素都需要向后排列。
二叉查找树解决了连续结构插入新元素开销很大的问题,同时又保持着天然的二分结构。
二叉查找树存在一个极端情况: 当每次插入的元素都是二叉查找树中最大的元素,二叉查找树就会退化成了一条链表,查找数据的时间复杂度变成了 O(n)
由于树是存储在磁盘中的,访问每个节点,都对应一次磁盘 I/O 操作(假设一个节点的大小「小于」操作系统的最小读写单位块的大小),也就是说树的高度就等于每次查询数据时磁盘 IO 操作的次数,所以树的高度越高,就会影响查询性能。
二叉查找树由于存在退化成链表的可能性,会使得查询操作的时间复杂度从 O(logn) 升为 O(n)。
而且会随着插入的元素越多,树的高度也变高,意味着需要磁盘 IO 操作的次数就越多,这样导致查询性能严重下降,再加上不能范围查询,所以不适合作为数据库的索引结构。
4.什么是自平衡二叉树?
在二叉查找树的基础上增加了一些条件约束:每个节点的左子树和右子树的高度差不能超过 1。也就是说节点的左子树和右子树仍然为平衡二叉树,这样查询操作的时间复杂度就会一直维持在 O(logn) 。
除了平衡二叉查找树,还有很多自平衡的二叉树,比如红黑树,它也是通过一些约束条件来达到自平衡。
不管平衡二叉查找树还是红黑树,都会随着插入的元素增多,而导致树的高度变高,这就意味着磁盘 I/O 操作次数多,会影响整体数据查询的效率。
当树的节点越多的时候,并且树的分叉数 M 越大的时候,M 叉树的高度会远小于二叉树的高度。
5.什么是 B 树
为了解决降低树的高度的问题,后面就出来了 B 树,它不再限制一个节点就只能有 2 个子节点,而是允许 M 个子节点 (M>2),从而降低树的高度。
B 树的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶,所以 B 树就是一个多叉树。
假设 M = 3,那么就是一棵 3 阶的 B 树,特点就是每个节点最多有 2 个(M-1个)数据 和最多有 3 个(M个)子节点 ,超过这些要求的话,就会分裂节点。
假设我们在上图一棵 3 阶的 B 树中要查找的索引值是 9 的记录那么步骤可以分为以下几步:
与根节点的索引(4,8)进行比较,9 大于 8,那么往右边的子节点走;
然后该子节点的索引为(10,12),因为 9 小于 10,所以会往该节点的左边子节点走;
走到索引为9的节点,然后我们找到了索引值 9 的节点。
可以看到,一棵 3 阶的 B 树在查询叶子节点中的数据时,由于树的高度是 3 ,所以在查询过程中会发生 3 次磁盘 I/O 操作。
B 树的每个节点都包含数据(索引+记录),而用户的记录数据的大小很有可能远远超过了索引数据,这就需要花费更多的磁盘 I/O 操作次数来读到「有用的索引数据」。
在我们查询位于底层的某个节点(比如 A 记录)过程中,「非 A 记录节点」里的记录数据会从磁盘加载到内存,但是这些记录数据是没用的,我们只是想读取这些节点的索引数据来做比较查询,而「非 A 记录节点」里的记录数据对我们是没用的,这样不仅增多磁盘 I/O 操作次数 ,也占用内存资源。
如果使用 B 树来做范围查询的话,需要使用中序遍历,这会涉及多个节点的磁盘 I/O 问题,从而导致整体速度下降。
6.什么是 B+ 树?
B+ 树就是对 B 树做了一个升级,MySQL 中索引的数据结构就是采用了 B+ 树
B+ 树与 B 树差异的点,主要是以下这几点:
叶子节点(最底部的节点)才会存放实际数据(索引+记录),非叶子节点只会存放索引;
所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表;
非叶子节点的索引也会同时存在在子节点中,并且是在子节点中所有索引的最大(或最小)。
非叶子节点中有多少个子节点,就有多少个索引;
下面通过三个方面,比较下 B+ 和 B 树的性能区别。
单点查询
B 树进行单个索引查询时,最快可以在 O(1) 的时间代价内就查到,而从平均时间代价来看,会比 B+ 树稍快一些。
但是 B 树的查询波动会比较大,因为每个节点即存索引又存记录,所以有时候访问到了非叶子节点就可以找到索引,而有时需要访问到叶子节点才能找到索引。
B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
插入和删除效率
B+ 树有大量的冗余节点,这样使得删除一个节点的时候,可以直接从叶子节点中删除,甚至可以不动非叶子节点,这样删除非常快 ,因此,B+ 树的插入和删除效率更高。
范围查询
因为 B+ 树所有叶子节点间还有一个链表进行连接,这种设计对范围查找非常有帮助
因此,存在大量范围检索的场景,适合使用 B+树,比如数据库。而对于大量的单个索引查询的场景,可以考虑 B 树,比如 nosql 的MongoDB。
7.MySQL 中的 B+ 树
Innodb 存储引擎,是采用了 B+ 树作为了索引的数据结构。
但是 Innodb 使用的 B+ 树有一些特别的点,比如:
B+ 树的叶子节点之间是用「双向链表」进行连接,这样的好处是既能向右遍历,也能向左遍历。
B+ 树节点内容是数据页,数据页里存放了用户的记录以及各种信息,每个数据页默认大小是 16 KB。
Innodb 根据索引类型不同 ,分为聚簇 和二级索引。他们区别在于:
因为表的数据都是存放在聚簇索引的叶子节点里,所以 InnoDB 存储引擎一定会为表创建一个聚簇索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个,而二级索引可以创建多个。