B-Tree实际上是一个术语,NDB使用了T-Tree存储索引,InnoDB使用B+Tree。B-Tree通常意味着所有的值是按顺序存储的,B-Tree索引能加快数据的访问速度,是因为存储引擎不再需要全表扫描来获取数据,而是从根节点向叶子结点搜索,通过匹配当前结点的值和要查找的值来确定是否继续向下查找。
基于哈希表实现,对于每一条数据,存储引擎都会对所有的索引列计算一个哈希码,不同的键值行计算的哈希码也不会一样。哈希索引将所有的哈希码都存到索引中,同时在哈希表中存储指向每一个数据行的指针。在MySQL中唯一支持哈希索引的是Memory引擎(InnoDB引擎有一个“自适应哈希索引,当InnoDB注意到某些索引值被使用频繁后,它会在内存中基于B-Tree索引之上再创建一个哈希索引,所以InnoDB的B-Tree索引也有哈希索引的一些优点”)。
MyISAM支持空间索引,空间索引无需前缀查询,查询时可以从任意维度来组合查询。
全文索引用来查找全文中的关键值。而不是直接来比较索引中的值,全文索引适用于match against操作而不是where的操作。
如果索引很长的字符列,通常可以索引开始的部分字符,这样可以大大节约索引空间。当索引的选择性是1,性能是最好的。对于blob、text或者很长的varchar类型的列必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。前缀索引的语法如下:
alter table stuGoldData add key (planId(3))
注意:前缀索引可以使查询更轻量,但MySQL无法使用前缀索引做order by 和 group by,也无法使用前缀索引做覆盖扫描。
在多个列建立独立的索引大部分情况下并不能提高MySQL的查询性能。MySQL5.0引入索引合并,可以定位多个索引列。注意:当服务器对多个索引做联合操作时通常会耗费大量CPU和内存。
select * from stuOnlineData where classId = 10086 and planId = 110112;
这时候是创建一个(classId, planId)索引还是创建(planId, classId)可以跑一些查询来确定值在列中的分布情况来确定表中值的分布情况。
select sum(classId = 10086), sum(planId = 110112) from stuOnlineData;
sum(classId=10086):10000000000000000
sum(planId=110112):19
所以我们需要把planId的值放在前面,因为planId的数量更小。
如果索引的叶子结点已经包含要查询字段的值,即索引包含(覆盖)所有需要查询的字段的值就叫做副高索引。使用副高索引的好处:
MySQL有两种方式可以生成有序的结果:通过排序操作或者按索引扫描。按照索引操作是很快的只需从一个索引记录指向下一个紧接着的记录,但如果不能覆盖到查询所需的全部列,那就不得不每扫描一条索引就回表查询一次对应的行,杂合基本上是随机IO。如果explain出来的type的值为"index",则说明MySQL使用了索引扫描排序。
MyISAM使用前缀压缩来减少索引大小,从而让更多的索引放入内存。默认情况下只压缩字符串,也可以通过参数设置int等整数类型被压缩。MyISAM压缩的方式是存储前缀字节数和剩余的不同后缀部分。例如第一个索引值是“perform”,第二个索引值是“performance”,那么第二个索引值压缩格式就是“7,ance”这种形式。
在相同的列上按照相同的顺序创建相同的索引会增加额外维护的成本,优化器工作的时候会增加额外的判断和执行过程。要避免这种创建。
从未使用的索引会增加查询的成本,我们应该查询一段时间内每个索引的使用频率,删除这些从未使用的索引。
尽管InnoDB的行锁效率很高,再使用索引时也要避免不必要的行锁,这回带来额外开销。例如:
select * from stuData where stuId < 5 and stuId >1 for update;
这条查询仅仅返回3条数据,但获取了4行的排他锁。这是因为在执行 where stuId < 5的时候存储引擎就已经返回1-4行的数据了,所以获取了1-4的行锁。
如上图,我们创立一个“二叉索引树”。二叉索引树特点就是任何结点的左孩子结点都小于当前结点,右叶孩子结点点都大于当前结点。 假如我们需要查找结点值为12的值,需要3次比较,查找流程如下:
如果上述的二叉查找树的插入序列是有序的(升序或者降序),二叉查找树会退化成链表,复杂度从O(logn)降为O(n)插入数据是(5,7,8,10,12,13,17)如下图所示:
为了避免这种情况就出现了具有平衡能力的二叉搜索树,其中AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logN)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。上图就是一个平衡二叉树。
如果我们采取上面的两种树的结构存储数据,那我们查找一个结点就需要从磁盘中读取一个结点(也就是一个磁盘块,从磁盘中读取数据都是按照磁盘块来读)。如果数据量非常大,二叉树的结点会非常多,树的高度将会变得非常大,查找数据将会进行多次磁盘IO效率很低。为了解决这个问题,我们找寻一种单个结点存储多个键值和数据的树状结构,B树可以满足所有要求。
从上图可以看出,B树相对于平衡二叉树,每个结点存储了更多的键值(key)和数据(data),并且每个结点拥有更多的子节点,子节点的个数称为阶,上述 图中的B树为3阶B树,高度也会很低。查找id=28的用户信息,所需要的流程如下:
相对于B树,B+树只有叶子结点存储数据,非叶子结点仅存储键值,增加了数据顺序访问指针,每个结点都指向相同结点的地址(B树的结点不仅存键值也会存储数据)。选用B树的原因在于数据库中页的大小是固定的,如果将键值和数据分开存储将会存储更多的值,这样查找数据时IO次数将再次减少,查询效率变快。数据页之间通过双向链表连接以及叶子结点中数据之间通过单向链表的连接方式可以快速找到表中所有的数据。
任何不考虑应用场景的设计都不是最好的设计,当我们明确的定义了使用 MySQL 时的常见查询需求并理解场景之后,再对不同的数据结构进行选择就成了理所当然的事情,当然 B+ 树可能无法对所有 OLTP 场景下的查询都有着较好的性能,但是它能够解决大多数的问题。
我们在这里重新回顾一下 MySQL 默认的存储引擎选择 B+ 树而不是哈希或者 B 树的原因:
O(1)
的单数据行操作性能,但是对于范围查询和排序却无法很好地支持,最终导致全表扫描;如果想要追求各方面的极致性能也不是没有可能,只是会带来更高的复杂度,我们可以为一张表同时建 B+ 树和哈希构成的存储结构,这样不同类型的查询就可以选择相对更快的数据结构,但是会导致更新和删除时需要操作多份数据。
上图就是聚簇索引,我们想要查询id>=18并且id<40的用户数据,SQL语句如下(id为主键):
select *from user where id >= 18 and id < 40;
具体查找过程如下:
键值匹配跟聚簇索引的流程是一样的,-左面的值是键值,右面的值是主键值,如果我们要查找幸运数字是33的用户信息,对应的sql语句如下:
select * from user where luckNum = 33;
最终我们查到luckNum值为33的主键值是47,然后我们再回到聚簇索引中查找数值。