【MySQL】MySQL索引优化——从原理分析到实践对比(分页优化+COUNT优化)

分页优化

由于limit的查询方式,因此使用limit进行分页效率并不高

我们举个🌰:limit 1000,10

实际执行的时候,会从头开始查询到1010条数据,然后再舍弃掉前1000条。。。没错就手笔是这么阔(˶‾᷄ ⁻̫ ‾᷅˵)

既然limit效率不高,那么如何进行优化呢?

不同场景的优化方式

  1. 自增且连续的主键(要求数据中间无缺失)

    WHERE id LIMIT 1000,10可以优化为WHERE id>1000 LIMIT 10

  1. 不一定连续且非按主键进行排序

举个🌰

优化前

sql 复制代码
select * from app_user ORDER BY name limit 100000,5;

​优化后

vbnet 复制代码
select * from app_user a inner join (select id from app_user order by name limit 100000,5) b on a.id = b.id;

​分别使用Explain分析下

​优化前,也用到了索引,但是是使用了range进行范围扫描

优化后, 有三行数据,第一行看着虽然走的是全局扫描,但是他是由第三行派生出的,即只有一"页"(本案例中只有5行数据),第二行可以看出使用的是主键进行关联索引,typeeq_refref的速度还快,第三行虽然是遍历索引,但是没有进行回表,而且只查询五个值即可,因而优化后速度快很多

JOIN关联优化

先介绍下驱动表和被驱动表的概念( ̄∇ ̄)/

简单理解先执行的就是驱动表,不同的join类型MySQL选择的驱动表不同

  • inner join 不确定,MySQL会自行判断(一般选数据量小的表做驱动表)
  • left join 左边👈的是驱动表
  • right join 右边👉的是驱动表

算法介绍

  1. 嵌套循环连接算法 Nested-Loop Join(NLJ)

使用索引字段进行关联的关联查询一般会使用NLJ

简单理解就是拿一张表(驱动表)中的所有数据,一次一次的去另一张表(被驱动表)中查找对应行,最后取出两张表的结果合集

sql 复制代码
EXPLAIN select * from app_user a inner join app_user_copy1 b on a.id = b.id;

上面SQL执行的大致流程如下:

  1. 把b表中读取一行数据(b表有过滤条件会从过滤后的结果中读取)
  2. 取出关联字段(id)去a表中查找较
  3. 取出a表中满足条件的行,和b表中获取到的结果合并,返回
  4. 重复上面👆3个步骤
  1. 基于块的嵌套循环连接算法 Block Nested Loop Join(BNL)

使用非索引字段进行关联的关联查询一般会使用BNL

sql 复制代码
EXPLAIN select * from app_user a inner join app_user_copy1 b on a.age = b.age;

Extra中Using join buffer (hash join)表示使用BNL,可以看到两张表进行的都是全表扫描

上面SQL执行的大致流程如下:

  1. 把b表中的所有数据放入到join_buffer中(join_buffer是内存中的一块区域,默认大小256k,放不下就分段放)
  2. 把a表中的每一行数据取出来,跟join_buffer中的全部数据做比较
  3. 返回满足join条件的数据

为什么MySQL会根据关联字段是否有索引而使用不同的算法

首先,我们大致量化下两个算法总消耗

NLJ总消耗(有索引)

  • 磁盘扫描:a表行数 * 2(会先扫描索引之后直接扫描符合条件的数据行)
  • 内存判断:a表行数 * b索引

BNL总消耗(无索引)

  • 磁盘扫描:a表行数 + b表行数
  • 内存判断:a表行数 * b表行数(由于join_buffer中的数据是无序的,因此判断次数=a表行数 * b表行数)

如果在没索引的情况下使用NLJ,会导致磁盘扫描=a表行数 * b表行数,由于内存判断要比磁盘扫描快的多,因此在没索引的情况下,MySQL一般会选择BNL,而有索引则选择NLJ

优化方式

  1. 关联字段加索引

  2. 减少不必要的字段查询

  3. 加大join_buffer_size的大小(一次缓存的越多,内层扫描的次数就越少)

  4. 小表驱动大表(Explain结果集中小表在上)

    1. 可使用straight join设置左表驱动右表,不过只适用于inner join

    2. 尽量让MySQL的优化器自行判断(MySQL的优化器还是很稳的)

    3. 关于inexsits

      1. 当后半部分筛选出的结果集小于前面半部分,一般用in

        1. in可以理解为以后面部分的结果集的大小作为外层循环的遍历次数,做个简单的代换就是a IN b就相当于for(b.size){a},因此in会先执行b部分,b部分越小,也就相当于for循环次数越少
      2. 当后半部分筛选出的结果集大于前面半部分,一般用exsits

        1. exsits会先执行exsits前面的部分,做个简单的代换就是a EXSITS b就相当于for(a.size){b},即a部分越小,for循环次数越少

值得注意的是,这里的小表的"小"是指关联的表们分别按照各自的过滤条件进行过滤后,参与join的数据量,而非原始数据量

说白了就是先执行的那部分所得到的结果集越小,执行效率越高

COUNT优化

关于count(*)count(1)count(id)count(字段)哪种效率最高?

自问自答:

  • 字段有索引count(*)约等于count(1)>count(字段)>count(id)

    • 二级索引存储的数据比主键索引小
    • count(1)无需取字段,count(字段)需要取字段,理论上来说count(1)会比count(字段)快一点
    • count(*)被特别优化了下,按行累加,效率很高
  • 字段无索引count(*)约等于count(1)>count(id)>count(字段)

但其实MySQL优化到现在的版本,这四个的执行效率基本差不多,explain

​可以看到四条执行结果完全一致,SO你懂的(o^^o)/🎉

不过count(字段)跟其他3个有个很大的区别,当字段中有NULL时(如下图id5的数据行name的值为NULL

​我们分别执行下count(*)、count(1)、count(id)、count(name)

可以看到,当count某个字段时,如果该字段为NULL,则不会被统计到(其他三个都会统计到)

优化方式

  1. 查询MySQL自己维护的总行数

    1. 使用myisam存储引擎的表的总行数(不带WHERE的)会被myisam存储到磁盘上,查询无需计算,超级快
    2. innodb存储引擎由于MVCC机制,获取总行数需要实时计算
  2. 模糊获取

    1. 使用show table status like '表名'获取近似值,不准确,但很快并且无需额外操作
  3. 在Redis中维护总行数

    1. 插入删除操作都需要额外维护Redis,而且并非完全准确的
  4. 在数据库增加计数表

    1. 准确,但成本较高,插入删除操作都需要额外维护这张表
相关推荐
TDengine (老段)4 小时前
TDengine 数学函数 DEGRESS 用户手册
大数据·数据库·sql·物联网·时序数据库·iot·tdengine
TDengine (老段)4 小时前
TDengine 数学函数 GREATEST 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
@yanyu6664 小时前
idea中配置tomcat
java·mysql·tomcat
安当加密5 小时前
云原生时代的数据库字段加密:在微服务与 Kubernetes 中实现合规与敏捷的统一
数据库·微服务·云原生
爱喝白开水a5 小时前
LangChain 基础系列之 Prompt 工程详解:从设计原理到实战模板_langchain prompt
开发语言·数据库·人工智能·python·langchain·prompt·知识图谱
想ai抽5 小时前
深入starrocks-多列联合统计一致性探查与策略(YY一下)
java·数据库·数据仓库
武子康5 小时前
Java-152 深入浅出 MongoDB 索引详解 从 MongoDB B-树 到 MySQL B+树 索引机制、数据结构与应用场景的全面对比分析
java·开发语言·数据库·sql·mongodb·性能优化·nosql
longgyy5 小时前
5 分钟用火山引擎 DeepSeek 调用大模型生成小红书文案
java·数据库·火山引擎
ytttr8736 小时前
C# 仿QQ聊天功能实现 (SQL Server数据库)
数据库·oracle·c#
盒马coding7 小时前
第18节-索引-Partial-Indexes
数据库·postgresql