- key字段代表实际走的是哪个索引,其值是name_score,说明走的是name_score这个索引。
- type字段代表了访问表的方式,其值ref说明是二级索引等值匹配,符合我们的查询。
把SQL中的*修改为NAME和SCORE,也就是SELECT name_score联合索引包含的两列:
EXPLAIN SELECT NAME,SCORE FROM person WHERE NAME='name1'
再来看看执行计划:
可以看到,Extra列多了一行Using index的提示,证明这次查询直接查的是二级索引,免去了回表。
原因很简单,联合索引中其实保存了多个索引列的值,对于页中的记录先按照字段1排序,如果相同再按照字段2排序,如图所示:
图中,叶子节点每一条记录的第一和第二个方块是索引列的数据,第三个方块是记录的主键。如果我们需要查询的是索引列索引或联合索引能覆盖的数据,那么查询索引本身已经“覆盖”了需要的数据,不再需要回表查询。因此,这种情况也叫作索引覆盖。我会在最后一小节介绍如何查看不同查询的成本,和你一起看看索引覆盖和索引查询后回表的代价差异。
最后,我和你总结下关于索引开销的最佳实践吧。
第一,无需一开始就建立索引,可以等到业务场景明确后,或者是数据量超过1万、查询变慢后,再针对需要查询、排序或分组的字段创建索引。创建索引后可以使用EXPLAIN命令,确认查询是否可以使用索引。我会在下一小节展开说明。
第二,尽量索引轻量级的字段,比如能索引int字段就不要索引varchar字段。索引字段也可以是部分前缀,在创建的时候指定字段索引长度。针对长文本的搜索,可以考虑使用Elasticsearch等专门用于文本搜索的索引数据库。
第三,尽量不要在SQL语句中SELECT *,而是SELECT必要的字段,甚至可以考虑使用联合索引来包含我们要搜索的字段,既能实现索引加速,又可以避免回表的开销。
不是所有针对索引列的查询都能用上索引在上一个案例中,我创建了一个name score的联合索引,仅搜索name时就能够用上这个联合索引。这就引出两个问题:
- 是不是建了索引一定可以用上?
- 怎么选择创建联合索引还是多个独立索引?
首先,我们通过几个案例来分析一下索引失效的情况。
第一,索引只能匹配列前缀。比如下面的LIKE语句,搜索name后缀为name123的用户无法走索引,执行计划的type=ALL代表了全表扫描:
EXPLAIN SELECT * FROM person WHERE NAME LIKE '%name123' LIMIT 100
把百分号放到后面走前缀匹配,type=range表示走索引扫描,key=name_score看到实际走了name_score索引:
EXPLAIN SELECT * FROM person WHERE NAME LIKE 'name123%' LIMIT 100