前面一篇《MySQL性能调优参考》,文章中提到优化的几个技巧,比如数据类型的使用、范式和反范式的合理使用、索引的使用及其使用的注意事项等等。其中我们接触最多的就是索引,你可能知道索引的底层结构是B+Tree、使用索引要遵守最左匹配原则,那你知道为什么要用B+Tree、为什么使用索引有那么多注意事项吗?所以还是要知其然知其所以然,看完这篇文章你就懂了。
首先,MySQL索引的存储不仅仅只有B+Tree的结构,还有Hash和全文,这个在创建索引时可以指定。
MySQL中常用的InnoDB存储引擎默认使用B+Tree结构,毕竟使用MySQL时范围查找的场景是最多的,当然如果等值查询比如热点数据这种场景可以使用Hash索引,如果有大量的文本数据需要搜索和处理,使用全文索引是一个常见的选择。这里只对B+Tree索引展开介绍,如果不了解B+Tree的可以先了解下前置知识《常见的数据结构及应用》。
要想知道在使用索引为什么要有那么多的注意事项和原则,我们需要先了解一下数据和索引的关系。接下来我通过一个简单例子,说明一下B+Tree索引在存储数据中的具体实现。
先创建一张商品表,设置id为主键:
- CREATE TABLE `goods` (
- `id` int PRIMARY KEY NOT NULL,
- `goods_no` varchar(20) DEFAULT NULL,
- `goods_name` varchar(255) DEFAULT NULL,
- `goods_price` decimal(10, 2) DEFAULT NULL
- );
表中的数据如下:
在向MySQL插入一行数据时,默认情况下,会根据主键字段的数据作为索引键值构建B+Tree索引,这个过程会遵循B+Tree的规则。goods表中的这些数据在B+Tree中的逻辑结构如下图
可以看到在非叶子节点上只存放了主键列的值,而叶子节点存放了主键对应的整行数据,这种索引又叫「聚簇索引」 也叫「主键索引」 。当sql句为 select id from goods 或者 select * from goods where id = 1 时都会通过这个索引进行查询到数据,这个可以通过执行计划看到
而我们自行设置的其他索引都称之为「普通索引」或「二级索引」或者是「非聚簇索引」,在向MySQL插入一行数据时除了会根据主键构建一个聚簇索引,还会根据其他索引列构建对应的普通索引。这里为goods_no、goods_name列创建一个普通索引后,表中的数据在这个索引中逻辑结构如下图
可以看到在普通索引中每个非叶子节点的键值存放的是索引列的数据,而叶子节点不仅存放了索引列的数据,还存放了对应的主键值。
那么,问:那么请问,当执行以下sql时会使用哪个索引?为什么?
- sql1:select id,goods_name from goods where goods_no='00001'
- sql2:select id,goods_name,goods_price from goods where goods_no='00001'
- sql3:select id,goods_name,goods_price from goods where goods_name='goods1'
sql1中使用了goods_no、goods_name创建的普通索引。因为查询的字段id和goods_name都在这个索引的叶子节点中,可以直接返回这些数据,所以不用再去其他地方查询,这个过程也叫做「覆盖索引」,执行计划中的Using index 就可以说明。
sql2中使用了普通索引和主键索引。因为要查询的goods_price在普通索引没有找到,所以在拿到主键后会去主键索引中再查找一次,这个过程叫做「回表」,也就是说要查两个 B+Tree 才能查到数据(如下图)。通常情况下要尽量避免回表操作,因为多一次扫描查询效率就会下降一些。
sql3没有使用索引,走的是全表扫描。首先条件字段并未使用到普通索引,因为不符合「最左匹配原则」。其次查询字段id,goods_name,goods_price在两个索引中都无法匹配,因此走了全表扫描,这种现象也叫「索引失效」。
不论是WHERE条件也好,查询字段也罢,是否使用索引或者使用哪个索引都是「优化器」来决定的,以下几个是优化器工作时索引失效的例子及说明:
所以我们不需要背索引失效场景以及索引使用的注意事项,只要知道数据在B+Tree索引中是怎样存储的、优化器是怎么选择索引的,这些那些的原则、注意事项还需要背吗?还是那句话,知其然知其所以然。