MySQL SQL优化之覆盖索引

前些天,有个同事跟我说:“我写了个SQL,SQL很简单,但是查询速度很慢,并且针对查询条件创建了索引,然而索引却不起作用,你帮我看看有没有办法优化?”。

我对他提供的case进行了优化,并将优化过程整理了下来。

优化前的表结构、数据量、SQL、执行计划、执行时间

表结构

CREATE TABLE `t_order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`order_code` char(12) NOT NULL,
`order_amount` decimal(12,2) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uni_order_code` (`order_code`) USING BTREE )
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

隐藏了部分不相关字段后,可以看到表足够简单, 并且在order_code上创建了唯一性索引uni_order_code。

数据量:316977

这个数据量还是比较小的,不过如果SQL足够差,一样会查询很慢。

SQL

select order_code,
order_amount from t_order order by order_code limit 1000;

哇,SQL足够简单,不过有时候越简单也越难优化。

执行计划

全表扫描、文件排序,注定查询慢!

那为什么MySQL没有利用索引(uni_order_code)扫描完成查询呢?因为MySQL认为这个场景利用索引扫描并非最优的结果。我们先来看下执行时间,然后再来分析为什么没有利用索引扫描。

执行时间:260ms


的确,执行时间太长了,如果表数据量继续增长下去,性能会越来越差。

全表扫描、文件排序与索引扫描、索引排序的区别

全表扫描、文件排序:

虽然是全表扫描,但是扫描是顺序的(不管机械硬盘还是SSD顺序读写性能都是高的),并且数据量不是特别大,所以这部分消耗的时间应该不是特别大,主要的消耗应该是在排序上。

利用索引扫描、利用索引顺序:

uni_order_code是二级索引,索引上保存了(order_code,id),每扫描一条索引需要根据索引上的id定位(随机IO)到数据行上读取order_amount,需要1000次随机IO才能完成查询,而机械硬盘随机IO的效率是极低的(机械硬盘每秒寻址几百次)。

根据我们自己的分析选择全表扫描相对更优。如果把limit 1000改成limit 10,则执行计划会完全不一样。

既然我们已经知道是因为随机IO导致无法利用索引,那么有没有办法消除随机IO呢?

有,覆盖索引。

优化后的索引、执行计划、执行时间

创建索引

ALTER TABLE `t_order`
ADD INDEX `idx_ordercode_orderamount` USING BTREE (`order_code` ASC, `order_amount` ASC);

创建了复合索引idx_ordercode_orderamount(order_code,order_amount),将select的列order_amount也放到索引中。

执行计划


执行计划显示查询会利用覆盖索引,并且只扫描了1000行数据,查询的性能应该是非常好的。

执行时间:13ms

从执行时间来看,SQL的执行时间提升到原来的1/20,已经达到我们的预期。

总结

覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。索引的字段不只包含查询列,还包含查询条件、排序等。

要写出性能很好的SQL不仅需要学习SQL,还要能看懂数据库执行计划,了解数据库执行过程、索引的数据结构等。

原文发布时间为:2017-12-19

本文作者:Mr船长

本文来自合作伙伴“数据和云”,了解相关信息可以关注“数据和云”微信公众号

时间: 2024-08-01 01:27:42

MySQL SQL优化之覆盖索引的相关文章

Mysql性能优化案例 - 覆盖索引分享_Mysql

场景 产品中有一张图片表,数据量将近100万条,有一条相关的查询语句,由于执行频次较高,想针对此语句进行优化 表结构很简单,主要字段: 复制代码 代码如下: user_id 用户ID picname 图片名称 smallimg 小图名称 一个用户会有多条图片记录 现在有一个根据user_id建立的索引:uid 查询语句也很简单:取得某用户的图片集合 复制代码 代码如下: select picname, smallimg from pics where user_id = xxx; 优化前 执行查

mysql sql优化之straight_join

在oracle中可以指定的表连接的hint有很多:ordered hint 指示oracle按照from关键字后的表顺序来进行连接:leading hint 指示查询优化器使用指定的表作为连接的首表,即驱动表:use_nl hint指示查询优化器使用nested loops方式连接指定表和其他行源,并且将强制指定表作为inner表. 在mysql中就有之对应的straight_join,由于mysql只支持nested loops的连接方式,所以这里的straight_join类似oracle中

MySql sql优化之order by desc/asc limit M

Order by desc/asc limit M是我在mysql sql优化中经常遇到的一种场景,其优化原理也非常的简单,就是利用索引的有序性,优化器沿着索引的顺序扫描,在扫描到符合条件的M行数据后,停止扫描:看起来非常的简单,但是我经常看到很多性能较差的sql没有利用这个优化规律,下面将结合一些实际的案例来分析说明: 案例一: 一条sql执行非常的慢,执行时间为: root@test 02:00:44   SELECT * FROM test_order_desc WHERE  END_TI

MySQL数据库优化技术之索引使用技巧总结_Mysql

本文实例总结了MySQL数据库优化技术的索引用法.分享给大家供大家参考,具体如下: 这里紧接上一篇<MySQL数据库优化技术之配置技巧总结>,进一步分析索引优化的技巧: (七)表的优化 1. 选择合适的数据引擎 MyISAM:适用于大量的读操作的表 InnoDB:适用于大量的写读作的表 2.选择合适的列类型 使用 SELECT * FROM TB_TEST PROCEDURE ANALYSE()可以对这个表的每一个字段进行分析,给出优化列类型建议 3.对于不保存NULL值的列使用NOT NUL

mysql sql优化实例

mysql sql优化实例 优化前: pt-query-degist分析结果: # Query 3: 0.00 QPS, 0.00x concurrency, ID 0xDC6E62FA021C85B5 at byte 628331 # This item is included in the report because it matches --limit. # Scores: V/M = 0.19 # Time range: 2016-09-24T15:14:24 to 2016-10-0

【MySQL】性能优化之 覆盖索引

  一个包含查询所需的字段的索引称为 covering index 覆盖索引.MySQL只需要通过索引就可以返回查询所需要的数据,而不必在查到索引之后进行回表操作,减少IO,提供效率.    当你对一个sql 使用explain statement 查看一个sql的执行计划时,在EXPLAIN的Extra列出现Using Index提示时,就说明该select查询使用了覆盖索引. [使用场景] 生产过程中遇到的一个例子,且先不讨论 count(字段)还是 count(*)   root@yang

SQL优化 · 经典案例 · 索引篇

Introduction 在这些年的工作之中,由于SQL问题导致的数据库故障层出不穷,下面将过去六年工作中遇到的SQL问题总结归类,还原问题原貌,给出分析问题思路和解决问题的方法,帮助用户在使用数据库的过程中能够少走一些弯路.总共包括四部分:索引篇,SQL改写篇,参数优化篇,优化器篇四部分,今天将介绍第一部分:索引篇. 索引问题是SQL问题中出现频率最高的,常见的索引问题包括:无索引,隐式转换.当数据库中出现访问表的SQL无索引导致全表扫描,如果表的数据量很大,扫描大量的数据,应用请求变慢占用数

SQL优化基础 使用索引(一个小例子)_MsSql

按照本文操作和体会,会对sql优化有个基本最简单的了解,其他深入还需要更多资料和实践的学习: 1. 建表: 复制代码 代码如下: create table site_user ( id int IDENTITY(1,1) PRIMARY KEY, [name] varchar(20), code varchar(20), date datetime ) 2. 插入8万条数据 复制代码 代码如下: declare @m int set @m=1 while @m<80000 begin INSER

SQL优化基础 使用索引(一个小例子)

按照本文操作和体会,会对sql优化有个基本最简单的了解,其他深入还需要更多资料和实践的学习: 1. 建表: 复制代码 代码如下: create table site_user ( id int IDENTITY(1,1) PRIMARY KEY, [name] varchar(20), code varchar(20), date datetime ) 2. 插入8万条数据 复制代码 代码如下: declare @m int set @m=1 while @m<80000 begin INSER