先看原始SQL(简化版):
随手EXPLAIN了一下,好家伙,type显示ALL,全表扫描!key字段为NULL,根本没用到索引。再看rows列,扫描行数接近700万,不慢才怪。
仔细分析WHERE条件中的字段:register_time、level、last_login、status,还有个ORDER BY register_time。直觉告诉我应该在register_time上建索引,毕竟它既有范围查询又参与排序。但事情没这么简单。
先试着加了个单字段索引:
再次执行查询,发现虽然用上了这个索引,但效果不明显。原来这个索引只能帮我们快速定位到某个时间段的记录,后面的level、last_login、status过滤还是得回表逐条判断。
看来需要复合索引。这时候就要考虑字段的顺序了,经典的“最左前缀”原则得用上。把范围查询的字段放在后面可能更合适,因为范围查询之后的索引列会失效。
根据这个思路,我设计了这样的索引:
为什么这么设计?让我解释一下:
把等值查询的status和level放前面,因为它们都能充分利用索引
register_time虽然也是范围查询,但因为它要参与排序,所以必须出现在索引中,且顺序要和ORDER BY一致
last_login作为另一个范围查询条件放在最后
建完索引再EXPLAIN,效果立竿见影:type变成range,扫描行数从700万降到不到1万,Extra显示Using index condition。查询时间从原来的12秒多降到0.2秒左右!
不过还有个细节要注意,原来的BETWEEN查询我改成了:
虽然效果一样,但BETWEEN在某些情况下可能不会充分利用索引,这种写法更稳妥。
另外,由于查询中SELECT的字段不多,如果把这个索引改成覆盖索引,性能还能进一步提升:
这样连回表操作都省了,查询时间可以降到0.1秒以内。
经过这番优化,业务方终于不再抱怨了。总结几点经验:一是别盲目建单字段索引,要多考虑复合索引;二是索引字段顺序很重要,等值在前,范围在后;三是如果查询字段不多,尽量使用覆盖索引避免回表。当然每个场景都不一样,最终还是要用EXPLAIN来验证。
1309

被折叠的 条评论
为什么被折叠?



