CodeAshen's blog CodeAshen's blog
首页
  • Spring Framework

    • 《剖析Spring5核心原理》
    • 《Spring源码轻松学》
  • Spring Boot

    • Spring Boot 2.0深度实践
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
  • RabbitMQ
  • RocketMQ
  • Kafka
  • MySQL8.0详解
  • Redis从入门到高可用
  • Elastic Stack
  • 操作系统
  • 计算机网络
  • 数据结构与算法
  • 云原生
  • Devops
  • 前端
  • 实用工具
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • Reference
GitHub (opens new window)

CodeAshen

后端界的小学生
首页
  • Spring Framework

    • 《剖析Spring5核心原理》
    • 《Spring源码轻松学》
  • Spring Boot

    • Spring Boot 2.0深度实践
  • Spring Cloud

    • Spring Cloud
    • Spring Cloud Alibaba
  • RabbitMQ
  • RocketMQ
  • Kafka
  • MySQL8.0详解
  • Redis从入门到高可用
  • Elastic Stack
  • 操作系统
  • 计算机网络
  • 数据结构与算法
  • 云原生
  • Devops
  • 前端
  • 实用工具
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • Reference
GitHub (opens new window)
  • MySQL8.0详解与实战

    • 第01章-数据库选型
    • 第02章-数据库设计
    • 第03章-访问数据库
    • 第04章-SQL开发之道
    • 第05章-SQL开发之术
    • 第06章-SQL优化
    • 第07章-索引优化
      • 2.1 索引的作用
      • 2.2 InnoDB 支持的索引类型
      • 2.3 Btree 索引的特点
      • 2.4 应该在哪些字段上建立索引
      • 2.5 如何选择复合索引键的顺序
      • 2.6 Btree 索引的限制
      • 2.7 索引使用的误区
    • 第08章-MySQL事务
  • MySQL面试指南

  • Redis从入门到高可用

  • Elastic-Stack

  • 数据库
  • MySQL8.0详解与实战
CodeAshen
2023-02-10
目录

第07章-索引优化

# 一、SQL 优化的手段

  • 优化 SQL 查询所涉及到的表中的索引
  • 改写 SQL 以达到更好的利用索引的目的

# 二、索引

# 2.1 索引的作用

告诉存储引擎如何快速的查找到所需要的数据。

举个例子,在下图课程表的技术方向字段上创建了索引,这些值就是一个个索引节点,指向实际存储数据的物理地址,通过 mysql 索引节点就可以找到物理地址 0001 和 0003 处存着 mysql 技术方向的课程记录。如果没有索引就需要逐一扫描物理块。

image-20210812104441554

正如上面所说,索引的作用是 告诉存储引擎如何快速的查找到所需要的数据。所以 MySQL 的索引是在存储引擎层实现的,而不是服务层实现的。这就决定了不同的存储引擎在索引的工作方式上是不同的,不同的存储引擎所支持的索引类型也不同,即使同一种索引,在不同存储引擎的底层实现上也是不同的。

# 2.2 InnoDB 支持的索引类型

  • Btree索引:最常用的索引类型
  • 自适应的 HASH 索引:这种索引的 InnoDB 存储引擎为了优化查询而自动建立的,不需要手动管理,所以不用太关注这类索引
  • 全文索引:用在字符串类型上,对中文支持不好,不建议使用(5.7 之后)
  • 空间索引:建立在点线面空间数据类型之上(5.7 之后)

# 2.3 Btree 索引的特点

  • 以 B+ 树的结构存储索引数据

    image-20210812105914224

    B+ 树本身是一种平衡的二叉树,每个叶子节点到根的距离都是相同的,所有索引节点都是按键值大小顺序放在同一层的叶子节点上的,并且每个叶子节点是通过指针连接的。

    这种结构是为了方便查找,但是不同的存储引擎实现有所不同,MyISAM 的 Btree 索引在叶子节点上存的是记录行的物理地址,而 InnoDB 的 Btree 索引在叶子节点上存的是数据行的主键的位置。

  • Btree 索引适用于全值匹配的查询,如

    class_name = 'MySQL', class_name in ( 'MySQL', 'Oracle' )

    in 条件是可以使用索引的,只有当 in 里的数据量过多时,MySQL 优化器可能认为还不如使用全表扫面方式性能好,才不会走索引。

  • Btree 索引适合处理范围查找,如 between...and、>、< 等

  • Btree 索引存在最左匹配原则

# 2.4 应该在哪些字段上建立索引

通常是需要结合需要使用的查询语句结合表中的列来建立索引的。通常在以下情况下的列建立索引:

  • WHERE 子句中的列

    并不是 WHERE 中的所有字段都需要建立索引,通常在筛选性好的字段上建立索引,即区分度高的字段。在联合索引中,也尽量将区分度高的字段放在前面。如下例子中,可以使用 count 方法评估字段的区分度。

    -- 查询性别为男,注册时间大于 2019-01-01 的用户
    SELECT user_nick FROM imc_user
    WHERE sex = 1 AND reg_time > '2019-01-01';
    
    -- 分析 sex 和 reg_time 字段的区分度
    SELECT 
    COUNT(DISTINCT sex) 性别数,
    COUNT(DISTINCT DATE_FORMAT(reg_time, '%Y-%m-%d')) 注册时间数,
    COUNT(*) 总记录数,
    COUNT(DISTINCT sex) / COUNT(*) 性别占记录数比例,
    COUNT(DISTINCT DATE_FORMAT(reg_time, '%Y-%m-%d')) / COUNT(*) 注册时间占记录数比例
    FROM imc_user;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    image-20210812112502961

  • 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段

    在 ORDER BY、GROUP BY 字段上建立索引可以提高排序性能,避免使用临时表。但并不是所有的情况 ORDER BY 都能使用索引,只有满足以下条件才能走索引:

    • 索引列的顺序和 ORDER BY 中字段顺序相同
    • 索引列中的方向和 ORDER BY 中字段指定的顺序相同
    • 在多个表的关联字段中,ORDER BY 中的字段要全部在关联表的第一张表中
  • 多表 JOIN 的关联列

# 2.5 如何选择复合索引键的顺序

  • 区分度最高的列放在联合索引的最左侧

  • 使用最频繁的列放到联合索引的最左侧

  • 尽量把字段长度小的列放在联合索引列的最左侧

    注:对于 innoDB 的数据页默认大小是 16K,键值的长度大小越小,一页所能承载的记录就越多,相应查询的 IO 性能就越好。详细可参考 InnoDB 数据页 (opens new window)

# 2.6 Btree 索引的限制

  • 必须符合最左匹配原则
  • NOT IN 和 <> 条件,无法使用索引
  • 索引列上使用表达式或函数,无法使用索引

# 2.7 索引使用的误区

  • 索引越多越好(x)

    索引不是越多越好,索引可以增加查询效率,但是会降低插入和更新的效率。甚至另一些情况会降低查询效率,这是因为 MySQL 优化器在优化查询的时候,会根据统计信息,对每一个可以用到的索引进行评估,以生成一个最优的执行计划,而如果我们同时拥有很多索引可以用于查询的话,这就增加了 MySQL 生成查询计划的时间,这样也就降低了 SQL 的查询性能。

  • 使用 in 列表查询不能使用索引

    如果使用 or 运算符关联多个条件的话,可能是无法用到索引的,但是 in 列表不同于 or,是可以用到索引的。但是如果满足 in 列表条件的数据过多,还不如全表扫面的时候,MySQL 优化器就会选择不走索引。

  • 查询过滤顺序必需同索引键顺序相同才可以使用到索引

    WHERE 条件字段只要能满足复合索引最左匹配原则,不需要按从左到右顺序编写 WHERR 子句,MySQL 优化器会会自动调整顺序以适应索引键值顺序,从而正确使用索引。

# 三、SQL 改写

  • 使用 outer join 代替 not in

    -- 查询出不存在课程的分类名称
    SELECT class_name
    FROM imc_class
    WHERE class_id NOT IN (
    	SELECT class_id FROM imc_course
    )
    
    -- 使用左外连接,筛选右表为 null 的数据行
    SELECT class_name
    FROM imc_class a
    LEFT JOIN imc_course b ON a.class_id = b.class_id
    WHERE b.class_id IS NULL 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    上述优化手段在 MySQL 8.0 的版本中可以由 MySQL 优化器自行完成。

  • 使用 CTE 代替子查询(8.0 新功能)

    使用子查询和 CTE 都会生成临时表,只不过 CTE 生成的临时表是一个匿名临时表,并且可以多次引用。

  • 拆分复杂大 SQL 为多个简单小 SQL

  • 巧用计算列优化查询(5.7+)

    -- 需求:查询对于内容,逻辑和难度三项评分之后大于28分的用户评分。
    EXPLAIN 
    SELECT * FROM imc_classvalue 
    WHERE (content_score + level_score + logic_score) > 28
    
    -- 添加计算列字段
    ALTER TABLE imc_classvalue ADD COLUMN total_score DECIMAL(3,1) AS (content_score + level_score + logic_score);
    -- 在计算列上添加索引
    CREATE INDEX idx_totalScore ON imc_classvalue(total_score);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
编辑 (opens new window)
上次更新: 2023/06/04, 12:34:19
第06章-SQL优化
第08章-MySQL事务

← 第06章-SQL优化 第08章-MySQL事务→

最近更新
01
第01章-RabbitMQ导学
02-10
02
第02章-入门RabbitMQ核心概念
02-10
03
第03章-RabbitMQ高级特性
02-10
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CodeAshen | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式