文章内容来自个人学习总结整理笔记、有不对或错误的地方欢迎指正,部分插图引用来自其他大佬的博客,感谢贡献!
1、索引是什么?
索引是帮助Mysql高效获取数据的排好序的数据结构,类似一本书的目录,通过索引可以快速找到对应的数据。
常见的索引数据结构有二叉树、红黑树、Hash、B-Tree、B+Tree,为什么MySQL数据库索引选择使用B+树???
不妨先来看看这些结构是什么样的
红黑树:红黑树是平衡二叉树的一种实现,会自动做平衡处理,即单边数量超过2个以上再插入的时候会做一次节点的平衡;可以看出当数据量特别大的时候,可能会造成树的层级特别高,也就意味着 IO 的次数越多,所以不是最优方案。
二叉树:左小右大,如果数据一直在右侧,则二叉树会失去效果,变成链表的结构。如下图所示,当数据量特别大或者极端情况下,索引的意义也不大,也会有扫描全表的可能性。
Hash:其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引,但是Hash不能进行范围查询和排序等操作,所以只在一些固定的情况下会用该索引算法。
B树和B+树:在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。
同时树中每个结点可以有多个孩子,意味着可以放更多的索引;(当容量大于15/16时会做一次平衡)
再来看问题,为什么用B/B+树这种结构来实现索引呢?
其实不难看出红黑树等结构也可以用来实现索引,但是文件系统及数据库系统普遍使用B/B+树结构来实现索引。mysql是基于磁盘的数据库,索引是以索引文件的形式存在于磁盘中的,索引的查找过程就会涉及到磁盘IO(为什么涉及到磁盘IO请看文章后面的附加理解部分)消耗,磁盘IO的消耗相比较于内存IO的消耗要高好几个数量级,所以索引的组织结构要设计得在查找关键字时要尽量减少磁盘IO的次数。
2、MyISAM索引与InnoDB索引的区别?
InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。
InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效.
InnoDB支持hash索引,不支持全文索引,MyISAM则相反。
InnoDB支持事务、外键、MyISAM不支持
InnoDB可以支持行级锁、表级锁,MyISAM只支持表级锁。
3、MyISAM和InnoDB存储引擎的数据结构
这个问题不妨先看看这2种引擎的文件在磁盘上的表现形式。
使用ISAM存储引擎和InnoDB存储引擎的表在磁盘上具有不同的扩展名,其所表示的意义也不一样,ISAM存储引擎的表在磁盘上会有3个不同的扩展文件,.MYD、.MYI、.frm;以表名叫student的表为例,他们说代表的意义如下:
student.frm:代表framework,存储的是表结构
student.MYD:D是Data的简写,存储的是数据行的记录
student.MYI:I就是Index,存储的是表里面的索引字段
如果要查找的字段是索引,则会通过MYI找到MYD,根据MYI存储的文件磁盘地址,直接定位到MYD;即ISAM引擎是将索引和数据分开存在2个文件。
Innodb存储引擎的表在磁盘上只有一个文件.idb;以表名叫teacher存储引擎为InnoDB的表为例
teacher.idb:i是index的缩写,db是Data base的简写,存储的是索引和数据行的记录;可以看出innodb是将索引和数据存在一个文件中的。
可以看到主键索引的叶子节点包含了完整的数据记录,非主键索引叶子节点存储的是主键的值,也就是说使用非主键索引查询数据会先找到该数据的主键值,然后通过主键找到对应的数据。
4、为什么InnoDB表必须要有主键,并且推荐使用整型的自增主键?
InnoDB使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:
这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置;此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
因此,只要可以,请尽量在InnoDB上采用自增字段做主键。
5、主键自增带来的劣势是什么?
对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键上界会成为”热点”,因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。另一个热点可能是AUTO_INCREMENT锁机制:如果遇到这个问题,则可能需要考虑重新设计表或者应用,或者更改innodb_autoinc_lock_mode配置。
6、为什么非主键索引结构叶子节点存储的是主键值?
InnoDB索引非主键索引存储的是主键ID,这样可以保证数据一致性和节省存储空间,可以这么理解:商城系统订单表会存储一个用户ID作为关联外键,而不推荐存储完整的用户信息,因为当我们用户表中的信息(真是名称、手机号、收货地址···)修改后,不需要再次维护订单表的用户数据,同时也节省了存储空间。(主要因为innerDB的索引和数据是在一个文件中,如果非主键索引不用主键的值,那么就意味做需要存多份数据文件)
7、为什么Mysql大多数都是用B+tree索引,而不使用Hash索引?
使用hash索引可能会出现hash冲突,且hash索引不支持范围查找;同时Hash无法被用来避免数据的排序操作,所以大多数情况下不满足业务需求,只有在确定只用到精确查询等情况下可以考虑使用Hash索引。
8、什么是最左前缀原则?什么是最左匹配原则?
最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
最左前缀法则:如果索引了多列,要遵守最左前缀原则,指的是查询从索引的最左前列开始并且不跳过索引中的列。
如下图所示,如果联合索引的列是(id,job,time)则在查询的时候要遵守最左侧开始索引,即先按照第一行10002的字段比较、然后按照第二行Staff字段比较,接着是第三行时间来比较;如果有其他条件跳过则索引效果失效。
通俗理解口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用。
9、MySQL InnoDB一棵B+树可以存放多少行数据?
约2千万
在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是512字节,而文件系统(例如XFS/EXT4)他的最小单元是块,一个块的大小是4k,而对于我们的InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K。在mysql中新创建的一个表初始化大小默认就是16k。innodb的所有数据文件(后缀为ibd的文件),他的大小始终都是16384(16k)的整数倍。
那么现在我们需要计算出非叶子节点能存放多少指针,其实这也很好算,我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针;上面已经说了一页16k,转换成字节就是16*1024=16384;那14字节可以存放的数据量即16384/14=1170。那么可以算出一棵高度为2的B+树,能存放1170*16=18720条这样的数据记录。
根据同样的原理我们可以算出一个高度为3的B+树可以存放:1170*1170*16=21902400 条这样的记录。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。
下图创建好的表默认都是16k
10、MySql中char、varchar和Text的区别
char:存储定长数据很方便,CHAR字段上的索引效率级高,必须在括号里定义长度,可以有默认值,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间(自动用空格填充),且在检索的时候后面的空格会隐藏掉,所以检索出来的数据需要记得用什么trim之类的函数去过滤空格。
varchar:存储变长数据,但存储效率没有CHAR高,必须在括号里定义长度,可以有默认值。保存数据的时候,不进行空格自动填充,而且如果数据存在空格时,当值保存和检索时尾部的空格仍会保留。另外,varchar类型的实际长度是它的值的实际长度+1,这一个字节用于保存实际使用了多大的长度。
text:存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。
char和varchar一句话总结就是:“一个是时间换空间、一个是空间换时间”
11、百万级别或以上的数据如何删除
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。
所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)然后删除其中无用数据(此过程需要不到两分钟)删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。
12、超大分页怎么处理?
超大的分页一般从两个方向上来解决.
数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于
select * from table where age > 20 limit 1000000,10
这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为
select * from table where id in (select id from table where age > 20 limit 1000000,10)
.这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以
select * from table where id > 1000000 limit 10
效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.
从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.