SQL优化

80 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?

对于低性能的SQL语句的定位, 重要也是 有效的方法就是使用执行计划, MySQL提供了explain命令来

查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行

执行的过程中都会做很多相关的优化,对于查询语句,最重要的优化方式就是使用索引。 而执行计划

就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用

的索引的相关信息等。 执行计划包含的信息 id 有一组数字组成。表示一个查询中各个子查询的执行顺序;

id相同执行顺序由上至下。

id不同,id值越大优先级越高,越先被执行。

id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。

select_type 每个子查询的查询类型,一些常见的查询类型。 table 查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划id

partitions 表分区、表创建的时候可以指定通过那个列进行表分区。 举个例子:

1 create table tmp (

2 id int unsigned not null AUTO_INCREMENT,

3 name varchar(255),

4 PRIMARY KEY(id))

5 engine=innodb partition by key(id) partitions 5;

type(非常重要,可以看到有没有走索引) 访问类型 ALL 扫描全表数据 index 遍历索引 range 索引范围查找 index_subquery 在子查询中使用 ref

unique_subquery 在子查询中使用 eq_ref ref_or_null 对Null进行索引的优化的 ref fulltext 使用全文索

ref 使用非唯一索引查找数据 eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。

possible_keys 可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被

列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。

key 显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。 TIPS:查询中若使用了覆盖

索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中

key_length 索引长度

ref 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值 rows 返回估算的结果集数

目,并不是一个准确的值。

extra 的信息非常丰富,常见的有:

  1. Using index 使用覆盖索引

  2. Using where 使用了用where子句来过滤结果集

  3. Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。

  4. Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册

【推荐】SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是

consts 好。 说明: 1) consts 单表中 多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取

到数据。 2) ref 指的是使用普通的索引(normal index)。 3) range 对索引进行范围检索。 反例:

explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,

与全表扫描是小巫见大巫。

大表数据查询,怎么优化

  1. 优化shema、sql语句+索引;

  2. 第二加缓存,memcached, redis;

  3. 主从复制,读写分离;

  4. 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;

  5. 水平切分,针对数据量大的表,这一步 麻烦, 能考验技术水平,要选择一个合理的sharding key,

为了有好的查询效率,表结构也要改动,

做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全

部的表;

超大分页怎么处理?

超大的分页一般从两个方向上来解决.

数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age

20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然

后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in

(select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由

于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select

  • from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种, 但是核心思想

都一样,就是减少load的数据.

从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允

许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.

【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL并不是跳过offset

行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的

低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。 正例:先快速定位需要获取

的id段,然后再关联:

SELECT a.* FROM1 a,(select id from1 where 条件 LIMIT 100000,20) b w here

a.id=b.id