作者:刘晨
网名 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秒,需求是将执行降到秒级,
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不在索引中,
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,
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------+
| 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)的复合索引,
create index idx_test_02(c1, c2, c3) on test;
此时执行SQL,Extra显示Using index,说明用到了覆盖索引的特性,
+----+-------------+-------+------------+-------+-------------------------+-------------+---------+------+------+----------+-------------+
| 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优化,很多都可以用基础的知识解决,只有一小部分,需要一些技巧,或者更深层次的知识,但这些所谓的基础知识,"了解"和"理解",存在着区别,单从知识来讲,可能都知道是怎么个原理,但当碰到实际的场景,能不能将知识运用到实践中,就取决于对知识的理解程度了,这个不仅仅指数据库领域,其它任何领域,都是相通的,学习知识,重要的是能应用到实践中,能做到举一反三,这个的前提就是对知识是不是真正理解了,而不是停留在表面上。
因此,我们学习任何知识的时候,一定要强调理论和实践的结合,多积累经验,毕竟解决问题,才是我们大多数职场人学习的目标。