1.你能说说什么是索引吗?
在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
2.索引具体采用的哪种数据结构呢?
常见的MySQL主要有两种结构:Hash索引和B+ Tree索引,我们使用的是InnoDB引擎,默认的是B+树
3.既然提到InnoDB使用的B+ 树的索引模型,那么你知道为什么采用B+ 树吗?这和Hash索引比较起来有什么优缺点吗?
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上,这样,索引查找过程中就要产生磁盘IO消耗。因此,选择数据结构时,索引的结构组织要尽量减少查找过程中磁盘IO的存取次数。 磁盘预读可以通过IO效率(局部性原理),预读的长度一般为页的整数倍。数据库系统巧妙利用了磁盘预读原理,将一个节点的大小设为一个页,这样每个节点只需要一次IO就可以完全载入。每次新建节点时,直接申请一个页的空间,这样就保证了一个节点物理上也存储在一个页,加之计算机存储分配都是按页对齐的,就实现了一个节点只需要一次IO。 B树中一次检索最多需要h-1次IO(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般应用中,出度d是非常大的数字,因此h非常小(通常不超过3)。红黑树这种结构,h明显要深的多,由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性。红黑树的IO渐进复杂度也为O(h),效率明显比B树差很多。
B+树更适合外索引,原因和内节点出度d有关,d越大索引性能越好,而出度的上线取决于节点内key和data的大小,由于B+树内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。dmax = floor(pagesize / (keysize + datasize + pointsize)) (pagesize – dmax >= pointsize)
和Hash索引比较
1) Hash索引哈希索引适合等值查询,不适合范围查找,而B+树特别适合范围查找(特别是聚簇索引的时候)
2)Hash索引每次查询要加载所有的索引数据到内存当中,哈希索引没办法利用索引完成排序 ,而B+树只需要根据匹配规则选择对应的叶子数据加载即可
3)另外B+树引入了缓存机制 和 数据页技术来提升性能(不过理论上来说,这两个特性Hash索引也可以实现)
4)如果有大量重复键值的情况下,哈希索引的效率会很低,因为存在哈希碰撞问题
4.B+ Tree的叶子节点都可以存哪些东西?以及区别?
InnoDB的B+ Tree可能存储的是整行数据,也有可能是主键的值;
在 InnoDB 里,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引;
聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续聚集索引:物理存储按照索引排序;
聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序。非聚集索引:物理存储不按照索引排序;
非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序。
索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
优势与缺点:
聚集索引插入数据时速度要慢(时间花费在“物理存储的排序”上,也就是首先要找到位置然后插入),查询数据比非聚集数据的速度快。
5.聚簇索引查询会更快,为什么?
因为主键索引树的叶子节点直接就是我们要查询的整行数据了。而非主键索引的叶子节点是主键的值,查到主键的值以后,还需要再通过主键的值再进行一次查询;
6. 刚刚你提到主键索引查询只会查一次,而非主键索引需要回表查询多次。(这个过程叫做回表)是所有情况都是这样的吗?非主键索引一定会查询多次吗?
覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。
当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。
如,表covering_index_sample中有一个普通索引 idx_key1_key2(key1,key2)。
当我们通过SQL语句:select key2 from covering_index_sample where key1 = 'keytest';的时候,就可以通过覆盖索引查询,无需回表。
7.创建索引需要考虑哪些因素
1)非空字段:应该指定列为NOT NULL,因为含有空值的列很难进行查询优化,因为他们使得索引、索引的统计信息以及比较运算更加复杂
2)取值离散大的字段
3)索引字段越小越好:数据库的数据存储以页为单位,一页存储的数据越多,一次IO操作获取的数据越大,效率越高
4)建立索引的时机:若表中的某字段出现在select、过滤、排序条件中,为该字段建立索引是值得的。5)对于like '%xxx'的模糊查询,普通的索引是无法满足的,需要建立全文索引。 6)对于有多个条件的,比如: "...where a=xxx and b=yyy","...where a=xxx order by b","...where a=xxx group by b"。需要使用组合索引。但是组合索引只能在SQL语句中满足"最左前缀"的条件下使用。且组合索引有一些副作用,如索引尺寸可能比数据本身大,因为组合索引的组合条目多。所以在实际应用中,要量身定做,使用慢查询分析工具分析。 7)开启索引缓存,直接在内存中查找索引,不用再磁盘中。8)建立索引是有代价的,当update、delete语句执行时,会使得索引更新,将耗掉更多的时间。可以使用mysqlreport报告,了解select、update、delete、insert、replace各语句所占的百分比。
9)表的数据量,一般实际开发中20W以上数据我们考虑索引
8.索引的规则:(在MySQL 5.6中,对索引做了哪些优化吗,主要是索引下推)
覆盖索引:普通索引的叶子节点存储的是主键值,如果要查询的内容不止是主键值,需要回表操作,即得到主键值之后还需要到主键索引进行查找得到记录值
最左前缀原则:利用最左前缀原则为最常用查询创建索引,来减少索引的个数,从而减少mysql开销
索引下推:mysql5.6之后引入了索引下推优化,相当于对联合索引做了查询优化,减少了回表操作,即对索引种包含的字段先做判断,直接过滤掉不满足条件的记录。
普通索引在命中记录后,会查找下一个,直到找到不满足条件的记录,而唯一索引在命中后直接返回
普通索引的更新会使用change buffer缓存结果,而不是每次更改都写入磁盘,因此对于时效不强的数据尽可能使用普通索引而非唯一索引
9.排查的时候,有什么手段可以知道有没有走索引查询呢
通过explain查看sql语句的执行计划,通过执行计划来分析索引使用情况
10.什么情况下会发生明明创建了索引,但是执行的时候并没有通过索引呢?
查询优化器?
一条SQL语句的查询,可以有不同的执行方案,至于最终选择哪种方案,需要通过优化器进行选择,选择执行成本最低的方案。
在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案。
这个成本最低的方案就是所谓的执行计划。优化过程大致如下:
1、根据搜索条件,找出所有可能使用的索引
2、计算全表扫描的代价
3、计算使用不同索引执行查询的代价
4、对比各种执行方案的代价,找出成本最低的那一个
11.索引的优化策略:
对于较长字符串的列的查询,可以考虑使用mysql5.7引入的虚拟列来创建索引
对于双M主备结构,auto_increment_increment可以都设置为2,一个写奇数一个写偶数,防止两个库的主键冲突
写入较多的情况下,主键数据类型可以设置为big int,有8个字节,上限可以到达264-1
对于比较大的表做查询,可以考虑使用临时表,然后给临时表创建索引
对于字符串字段可以创建前缀索引来节省空间,前缀长度可以使用count(distinct)来判断,尽可能高区分度
对于区分度不打的字符串,可以考虑使用逆序存储或者hash值索引,这种方法仅适用等值查找,范围扫描会导致全索引扫描
有时mysql的优化器会选错索引,可以使用force index来强制指定索引,也可以通过修改语句来引导优化器,当然也可以直接增加或删除索引来避免,也可以使用analyze table [表名]重新统计索引信息后再执行
在使用join时要使用被驱动表的索引(使用了NLJ算法),尽可能让小表(满足条件的数据量相对较小的表)做驱动表,因为被驱动表的索引查找可以最大限度减少查询次数
explain看到join语句的Extra中有Block Nested Loop(BNL)时表示被驱动表做了全表扫描,要避免这种写法,如果无法避免,则join_buffer_size可以设大一点让驱动表的数据可以加载多一点,从而减少被驱动表全表扫描的次数(因为join_buffer一次放不下驱动表数据时,会分多次装入后跟被驱动表的全表数据做对比)
12.索引的锁机制
默认加锁的基本单位是next-key锁,next-key锁本身是前开后闭的
查找过程中访问到的对象才会加锁,访问到就会加锁
索引上的等值查询,给唯一索引加锁时,next-key会退化为行锁(innodb)
索引上的等值查询,向右遍历且最后一个值不满足等值条件时,next-key会退化为区间锁(前开后开)
索引默认向后遍历,如果有order by … desc字段要求逆序排列,则会从后向前遍历
评论区