掘金 后端 ( ) • 2021-07-11 17:10
.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}

前言

最近在啃Mysql,我对Mysql的水平限于事务和隔离级别,更深入的就不了解了,所以在学习在补。我学习的资料主要是《从根儿上理解Mysql》这本书和丁奇老师的《Mysql45讲》,从《从根儿上理解Mysql》这本书看到InnoDB数据页的结构就觉得吃力,所以后来主要是看《Mysql45讲》,看的津津有味。所以在此做下自己的总结。

楔子

丁奇老师开篇第一句话就是:学技术千万不要直接陷入细节里,应该先鸟瞰其全貌,这样能够帮助从高维度理解问题。这句话我很认同。所以觉得学习Mysql也应该有个大致的框架,知道哪一块属于哪一部分,在脑海中有知识脉络。Mysql我觉得主要有四个部分:底层存储,部署架构,索引类型,数据备份原理。 面试问的问题也不外乎从这四部分问起。我这篇文章要总结的也是索引类型这部分。

有哪些常见的索引结构

索引是一种数据结构,为了提高数据查询的效率

哈希

哈希表其实就是一种以键-值(key-value)存储数据的结构

每个值要存放在哪个位置的下标是通过hash随机算出来的,而使用hash都知道会出现一种问题,那就是哈希冲突。因为可能会有多个不同的值被哈希到同一个位置。而解决哈希冲突的一种方法便是链式地址法,当有多个key值经过哈希函数的换算被哈希到同个位置,则在这个位置上维护一个链表,哈希出来在该位置的数据都放在这个链表中。需要注意的是,链表中的元素并不是有序的,因为不是有序,所以插入元素很快,但要查找元素时就会很慢,要全部扫描一遍。所以:哈希表只适用于等值查询,但不支持范围查询

有序数组

有序数组在等值查询和范围查询场景中的性能都很优秀,因为是有序的,自然可以用二分法,而二分法时间复杂度是O(log(N))。虽然查询效率高,但更新数据很麻烦,有序数组都这样,要中间插入一个数组就必须得挪动后面所有的数组。所以有序数组索引只适用于静态存储引擎,存储那些不会变的历史数据。

搜索树

学过数据结构都知道,二叉搜索树中序遍历时是个有序数组,所以查询的时间复杂度可以达到O(log(N))。为了维持O(log(N))的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是O(log(N)),所以会是平衡二叉搜索树。 但这只是在内存的情况,对于落盘持久化的数据,读一个数据块是很慢的。为了让一个查询尽量少地读磁盘,须让查询过程访问尽量少的数据块,也就是让树存储尽量多的数据,所以要使用N叉树,

InnoDB 使用的索引

在InnoDB中:

(1)主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)。
(2)非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引(secondary index),也是非聚簇索引。

这两句话得好好理解,创建一张表一定会定义一个主键,就算没有定义,innoDB也会默认创建一个Rowid做主键,树节点的key值就是某一行的主键,value是该行的其他数据。而新增索引相当于新增一棵B+树,所以这就意味着一张表里至少有一棵B+树,这棵树为主B+树。聚簇索引是以主键为key,叶子节点存储的是行记录,非聚簇索引是以定义的索引字段为key,叶子结点存储的是主键的值

那么主键索引和非主键索引有什么区别呢? 我们知道,主键索引的那棵B+树存储着整行的记录,如果我们想要查某一行记录,那通过以聚簇索引的B+树一下子就能找到,因为行记录本身就存储在这棵主B+树上,而通过非聚簇索引的B+树则需要先找到对应的主键的值,然后再回到主B+树去查找行记录,这个过程叫做回表。也就是说,基于非主键索引的查询需要多扫描一棵索引树,这给了我们一个SQL优化的思路:应该尽量使用主键查询。

该不该使用业务逻辑字段做主键?

我们知道二级索引的叶子节点会存储主键的值,如果某个业务逻辑字段很长,以该业务逻辑字段做主键,就会导致二级索引的叶子节点占用很多个字节,占用的空间也变大,所以这种情况是不适合用业务逻辑字段做主键的。
由此可知,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小

有些业务的场景需求是要求只有一个索引,并且该索引必须是唯一索引,针对这种情况,那表里就会只有一棵B+树,没有其他二级索引生成的B+树,也就不用考虑其他索引的叶子节点大小的问题,所以可以将业务逻辑字段设为主键,满足尽量使用主键查询的原则。

SQL优化的手段

知道了索引,那么自然就会想到如果对SQL进行优化。其实说到底,SQL优化就是尽量避免回表,减少回表的次数。一种方法就是覆盖索引,另一种是最左前缀原则。

覆盖索引

先要明白,覆盖索引并不是任何一种索引的名称,它只是一种SQL优化要遵循的原则,一种SQL性能优化的手段。
我尽量根据我的理解通俗地解释什么是覆盖索引,还是算了,引用MySQL45讲的话吧 image.png 覆盖索引就是尽量让SQL在查询时,在二级索引的B+树上就能查到想要的值,而不用再去主B+树上回表查询。 依据这样的原则指导,我们可以建立联合索引来将想要查询的字段覆盖。 很多联合索引的建⽴,就是为了⽀持覆盖索引,特定的业务能极⼤的提升效率。 从这也可以理解为什么平时写SQL时尽量不要用select *,因为这肯定是会回表的,没有做到覆盖索引

最左前缀原则

创建联合索引时需要遵循最左前缀原则。当你创建一个联合索引(a,b,c)时,索引项是按照索引定义里面出现的字段顺序排序的,所以你写SQL的时候为了满足最左前缀,利用索引来加速检索,也应该按照定义的字段顺序来写。比如where a=1 and b=1 and c=1,而不要where a=1 and c=1 and b=1。另提一句:最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。
那么创建联合索引要怎么安排字段的顺序呢?
先索引的复用能力,再考虑其他因素比如占用空间。如果你定义的索引顺序已经够用,不需要再维护另一个索引,自然是好的,其次如果这个因素不影响,除了这个联合索引外一定要维护另一个索引,则考虑选择占用空间小的索引。

索引下推

索引下推其实是Mysql5.6之后引入的一种减少回表次数的优化方式,其实不难理解。就是可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
怎么理解呢?有这么一条语句:

select * from table where name like '陈%' and age = 24 and height =170
复制代码

在MySQL5.6之前, 它的查询过程是先在非主键索引树上找到陈这个字段对应的许多个主键,因为是模糊查询,分别是ID1,ID2,ID3,然后再根据ID1去主键索引树回表查询ID1对应的行记录,根据ID2去主键索引树回表查询ID2对应的行记录,ID3同样如此,一个个回表分别要回表三次。
在MySQL5.6之后, 当在非主键索引树上找到ID1,ID2,ID3时,其实找到的不止这三个主键,也找到了age,(ID1,age=31),(ID2,age=21),(ID3,age=24),所以在回表之前它会先判断,age=31和age=21是不满足条件的,就不会再去回表了。

参考资料