【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. 准确,但成本较高,插入删除操作都需要额外维护这张表
相关推荐
努力算法的小明12 分钟前
SQL 复杂查询
数据库·sql
斗-匕15 分钟前
MySQL 三大日志详解
数据库·mysql·oracle
代码中の快捷键21 分钟前
MySQL数据库存储引擎
数据库·mysql
只因在人海中多看了你一眼22 分钟前
数据库体系
数据库
尘浮生44 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
六月闻君1 小时前
MySQL 报错:1137 - Can‘t reopen table
数据库·mysql
SelectDB技术团队1 小时前
兼顾高性能与低成本,浅析 Apache Doris 异步物化视图原理及典型场景
大数据·数据库·数据仓库·数据分析·doris
inventecsh1 小时前
mongodb基础操作
数据库·mongodb
白云如幻1 小时前
SQL99版链接查询语法
数据库·sql·mysql
爱吃烤鸡翅的酸菜鱼2 小时前
MySQL初学之旅(4)表的设计
数据库·sql·mysql·database