技术分享 | MySQL 覆盖索引优化案例一则

2023-02-02 16:50:24 浏览数 (1)

作者:刘晨

网名 bisal ,具有十年以上的应用运维工作经验,目前主要从事数据库应用研发能力提升和技术管理相关的工作,Oracle ACE(Alumni),腾讯云TVP,拥有 Oracle OCM & OCP 、EXIN DevOps Master 、SCJP 等国际认证,国内首批 Oracle YEP 成员,OCMU 成员,《DevOps 最佳实践》中文译者之一,CSDN & ITPub 专家博主,公众号"bisal的个人杂货铺",长期坚持分享技术文章,多次在线上和线下分享技术主题。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


最近有个MySQL的SQL性能问题,原理可能很基础,但考察的就是能不能将"显而易见"的知识应用到实践中。

经过脱敏的SQL如下所示,对test表中的c1列进行聚类,再通过SUM ... CASE WHEN...等函数进行统计,test表数据量500万,当前检索用时55秒,需求是将执行降到秒级,

代码语言:txt复制
SELECT c1,·
       SUM(CASE WHEN c2=0 THEN 1 ELSE 0 END) as folders,
       SUM(CASE WHEN c2=1 THEN 1 ELSE 0 END) as files,
       SUM(c3)
FROM  test
GROUP BY c1;

为了更好地说明,创建一张测试表,主键字段是id,除了c1、c2、c3字段外,还有其他字段,有很多索引,但和c1、c2、c3相关的,只是idx_test_01,c1作为前导列的复合索引,且c2和c3不在索引中,

代码语言:txt复制
CREATE TABLE test (
  id bigint(20) not null,
  c1 varchar(64) collate utf8_bin not null,
  c2 tinyint(4) not null,
  c3 bigint(20) default null,
  ...
  primary key(id),
  key idx_test_01(c1, ...)
  key ...
  ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

显而易见,如上SQL执行时,能用到的索引就只有idx_test_01,Extra是NULL,

代码语言:txt复制
 ---- ------------- ------- ------------ ------- --------------- ------------- --------- ------ ------ ---------- ------- 
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra |
 ---- ------------- ------- ------------ ------- --------------- ------------- --------- ------ ------ ---------- ------- 
|  1 | SIMPLE      | test  | NULL       | index | idx_test_01   | idx_test_01 | 206     | NULL |    1 |   100.00 | NULL  |
 ---- ------------- ------- ------------ ------- --------------- ------------- --------- ------ ------ ---------- ------- 
1 row in set, 1 warning (0.00 sec)

我们知道,MySQL的索引默认是聚簇索引(可以理解为Oracle的IOT索引组织表),针对当前仅有(c1, ...)这个复合索引,当执行检索时,即便能使用这个复合索引,他都需要执行两个操作,(1)访问(c1, ...)复合索引;(2)从该复合索引中得到主键id,再进行回表,根据主键id,得到相应数据。这个过程中,最需要消耗的,就是磁盘IO的资源。不仅需要访问(c1, ...)复合索引的数据,还需要回表,访问数据行。

设计索引应该考虑到整个查询,不单只是WHERE条件。索引是能高效找到数据的方式,但是如果使用索引可以直接得到列的数据,即索引的叶子节点中已经包含要查询的数据,就无需回表,读数据行了。如果一个索引包含(或者叫做覆盖)所有要查询的字段的值,就可以称之为"覆盖索引",但是要注意,只有B-tree索引可以用于覆盖索引。

覆盖索引能显著提高检索的性能,原因就是查询只需要扫描索引而无需回表,

1.索引条目通常远小于数据行大小,因此如果只需要扫描索引,就会极大地减少数据访问量。数据访问响应时间大部分花费在数据拷贝上,索引比数据更小,更容易全部放入内存中。

2.因为索引是按照列值的顺序存储的,所以范围查询会比随机从磁盘读取每一行数据消耗的IO少得多。

3.由于InnoDB的聚簇索引的特点,覆盖索引对InnoDB表特别有用,因为InnoDB的二级索引在叶子节点中保存了记录的主键值,所以如果二级索引能够覆盖查询,则可以避免对主键索引的二次查询。

在索引中满足查询的成本一般比查询记录本身要小得多。

因此,针对这条SQL,创建包含了(c1, c2, c3)的复合索引,

代码语言:txt复制
create index idx_test_02(c1, c2, c3) on test;

此时执行SQL,Extra显示Using index,说明用到了覆盖索引的特性,

代码语言:txt复制
 ---- ------------- ------- ------------ ------- ------------------------- ------------- --------- ------ ------ ---------- ------------- 
| id | select_type | table | partitions | type  | possible_keys           | key         | key_len | ref  | rows | filtered | Extra       |
 ---- ------------- ------- ------------ ------- ------------------------- ------------- --------- ------ ------ ---------- ------------- 
|  1 | SIMPLE      | test  | NULL       | index | idx_test_01,idx_test_02 | idx_test_02 | 204     | NULL |    1 |   100.00 | Using index |
 ---- ------------- ------- ------------ ------- ------------------------- ------------- --------- ------ ------ ---------- ------------- 
1 row in set, 1 warning (0.00 sec)

从执行效率上,原来跑55秒的语句,现在只需要2秒。

根据2-8原则,可能我们平时碰到的SQL优化,很多都可以用基础的知识解决,只有一小部分,需要一些技巧,或者更深层次的知识,但这些所谓的基础知识,"了解"和"理解",存在着区别,单从知识来讲,可能都知道是怎么个原理,但当碰到实际的场景,能不能将知识运用到实践中,就取决于对知识的理解程度了,这个不仅仅指数据库领域,其它任何领域,都是相通的,学习知识,重要的是能应用到实践中,能做到举一反三,这个的前提就是对知识是不是真正理解了,而不是停留在表面上。

因此,我们学习任何知识的时候,一定要强调理论和实践的结合,多积累经验,毕竟解决问题,才是我们大多数职场人学习的目标。

0 人点赞