MySQL 高性能索引使用策略

news2024/11/15 13:49:15

文章目录

  • 前置知识
  • 表准备
  • 一. 不在索引列上使用任何操作
  • 二. 联合索引字段列全值匹配
  • 三. 最佳左前缀法则
  • 四. 范围条件放最后
  • 五. 覆盖索引使用
  • 六. 不等于导致索引失效
  • 七. is null/not null 影响
  • 八. like 查询的使用
  • 九. 字符类型加引号
  • 十. OR关键字前后索引问题
  • 十一. 利用索引来做排序和分组
  • 十二. 升降序使用
  • 十三. 按主键顺序插入
  • 十四. 优化count查询
  • 十五. 优化limit分页
  • 十六. MySQL Null 说明

前置知识

MySQL 高性能索引创建策略

表准备

-- 表1
drop table a;
create table a
(
    id          int primary key auto_increment,
    user_name   varchar(10),
    sex         tinyint(2),
    age         int(10),
    birth       datetime,
    city        varchar(10),
    create_time bigint(20)
);

一. 不在索引列上使用任何操作

查询列不是独立的,MySQL不会走索引的。

独立的列:索引列不能时表达式的一部分,也不能是函数的参数。

如,对主键索引添加表达式操作:

-- MySQL无法自动解析这个表达式。属于用户行为、
-- 始终要将索引列单独放在比较符号一侧
explain select * from a where id + 1 = 2;

在这里插入图片描述

如,对索引进行函数操作:

-- 添加索引
drop index idx_index on a;
create index idx_index on a (city);

-- 查询
explain select birth from a where UPPER(city) = '1'

在这里插入图片描述


二. 联合索引字段列全值匹配

全值匹配:在建立联合索引前提下,搜索条件中的列和索引列一致。

-- 索引创建
drop index idx_index on a;
create index idx_index on a (city,user_name, age);

-- 查询
explain select * from a where city = '辽宁' and user_name = '张三' and age = 20;

建立三个索引列被查询语句引用。
在这里插入图片描述

延伸出一个疑问,几个搜索条件顺序对结果是否有影响?

-- 按照乱序条件全值匹配的搜索条件
explain select * from a where user_name = '张三' and age = 20 and  city = '辽宁';

通过执行计划可看出,并没有影响,因为MySQL通过查询优化器分析所搜条件并且按照索引中列的顺序决定先试用哪个作为搜索条件,后使用哪个搜索条件

在这里插入图片描述


三. 最佳左前缀法则

当搜索条件不满足联合索引全字段时,需要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。

原理是B+Tree中,联合索引中最左字段作为B+Tree范围的排序,所以搜索条件中必须出现左边的列才可以使用这个B+Tree索引。


比如,创建了index(a,b,c)一个联合索引,B+Tree的数据页和记录先按照a字段值进行排序,在a字段值相同情况下,才会对b字段值进行排序,也就是说a字段值不同的情况下,对应的b字段值是有可能无序的,而如果跳过a字段搜索条件,直接把b字段当作搜索条件是不现实的。

但如果你只想对b进行查询,那就重新再建立个索引即可。

而且需要注意的一点,使用联合索引中的列,搜索条件的各个列是联合索引从最左边连续的列。

以上的文案如果晦涩难懂,请看下面的4个示例进行实战理解。

⭐️创建索引

drop index idx_index on a;
create index idx_index on a (city,user_name, age);
  • 示例一

    利用最左前缀的字段city,单独作为查询条件

    explain select * from a where  city = '辽宁';
    

    key_len使用了33个字节,只使用了一个city作为索引,后面索引失效

    我们指定了city的字符长度是10,用的utf-8
    
    所以10 * 3 + 2 (varchar存储长度开销的字节) + 1(可为null,占用1个字节)
    

    在这里插入图片描述

  • 示例二

    利用最左前缀的字段city和下一个字段user_name作为搜索条件

    explain select * from a where  city = '辽宁' and user_name = '张三';
    

    key_len使用了66个字节,说明用到了cityuser_name作为索引,age索引失效

    city      varchar(10)
    user_name varchar(10)
    
    (10 * 3 + 2 + 1) + (10 * 3 + 2 + 1) = 66 byte
    

    在这里插入图片描述

  • 示例三

    使用最左前缀字段city和跳过索引列字段age作为搜索条件

    -- 查询条件谁前谁后,MySQL优化器会优化,不用care
    explain select * from a where  age = 20 and city = '辽宁' ;
    

    key_len使用了33个字节,说明只用到了cityage索引失效。

    city      varchar(10)
    age      int
    
    10 * 3 + 2 + 1= 33 byte;
    
    ps:
    如果用到age索引的话,会用int数据类型的4个字节,那么key_len应为33 + 4 = 37 byte
    

在这里插入图片描述

  • 示例四

    跳过最左前缀字段,用user_nameage作为搜索条件

    explain select * from a where  age = 20 and user_name = '张三' ;
    

    key_len为null,说明没走索引
    在这里插入图片描述


四. 范围条件放最后

这一点是针对联合索引,所有记录都是按照索引列的值按顺序排放。

比如创建个index(a,b,c),B+Tree的数据页和记录会按照a的字段值进行排序。

若对a进行区间内的范围查询,如a>1 and a< 10像这种的话

找到a = 1 的记录

找到a = 10的记录

由于这些记录是通过链表连起来的,所以他们之间的记录取出来很容易,通过这些索引页记录的bookmark,在到聚簇索引中回表查找对应行的完整记录。

但对于多个列之间的范围查询,只有对索引的最左字段的范围查询会用到B+Tree的索引。

⭐️创建索引

drop index idx_index on a;
-- birth     datetime    6  byte
-- user_name varchar(10) 33 byte
-- age       int         4  byte
create index idx_index on a (birth,user_name,age);
  • 示例一

    使用birthuser_name作为范围查询

    explain select * from a where  birth > '2004-07-28 00:00:00' and user_name > '张';
    

    key_len占用6个字节,只使用了birth而没使用user_name

    当`birth`值不等的情况下,`user_name`的顺序是无法确定的,所以MySQL优化器选择了只走了`birth`字段的索引。
    

    在这里插入图片描述

  • 示例二

    使用birth作为范围查询,user_name精准匹配

    explain select * from a where   birth > '2004-07-28 00:00:00' and user_name = '张三';
    

    key_len同样也是6字节,birth使用了索引,user_name索引失效

    查询中,`birth`进行范围查询查找的记录中可能并不是按照`user_name`列进行排序,所以在搜索条件中继续以`user_name`列进行查找时是用不到B+Tree索引的。
    
    - 所以对于一个联合索引,多个列的范围查询,只能用到最左边的列
    - 如果最左边的列是精准查找,则下一个列可以进行范围查询,以此类推
    

    在这里插入图片描述

  • 示例三

    对示例二进行求证,对birth精准查找,user_name范围查找,age精准匹配

    explain select * from a where   birth = '2004-07-29 00:00:00' and user_name > '张' and age = 20;
    

    key_len占用39个字节,说明age索引失效,证明了示例二的解释
    在这里插入图片描述


五. 覆盖索引使用

覆盖索引是非常重要的一个工具,能够极大提高性能。

三星索引里最重要的那颗星就是宽索引星(覆盖索引)。

覆盖索引带来的好处:索引条目通常远小于数据行大小,对于只读取索引,MySQL就会极大减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。覆盖索引对于IO密集型应用也有帮助,因为索引比数据更小,更容易全部放入内存中。

因为索引是按照列值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据I/O少得多。

对于InnoDB多聚簇索引,覆盖索引对InnoDB表作用页非常大,InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,就可以避免对主键索引的二次查询。

可以在select * 时,用指定所需的字段,并创建覆盖其对应的联合索引。


六. 不等于导致索引失效

MySQL在使用!=<>时无法使用索引,而进行全表扫描。

这是因为不等于操作符使得数据库无法准确地通过索引找到特定的行,因为索引是排序的,不等于操作符破坏了这种排序,使得数据库不得不进行全表扫描或者范围查询来找出满足条件的行。


七. is null/not null 影响

⭐️如果对字段a进行的单个字段索引设置,如index(a)

a字段不允许为null

  • is null 是MySQL直接表示Impossible WHERE(查询语句的WHERE子句永远为FALSE时,将会提示该额外信息)
  • is not null走的全表扫描

a字段允许为null

  • is null走的索引
  • is not null走的全表扫描

⭐️如果是联合索引,如index(a,b)

a字段不允许为null

  • is null是MySQL直接表示Impossible WHERE(查询语句的WHERE子句永远为FALSE时,将会提示该额外信息)
  • is not null走的全表扫描

a字段允许为null

  • is null走的索引,ref类型
  • is not null走的索引,range类型

小伙伴自己尝试即可。笔者就不做结果图的证明了。

但在设计表时,尽量不要声明为null。


八. like 查询的使用

创建index(a)

若使用a like ‘%xxx%’,会造成索引失效,全表扫描。

解决方式:

  1. 可以使用 a like ‘xx%’。

  2. 可以使用覆盖索引,进行优化

    drop index idx_index on a;
    create index idx_index on a (user_name,age);
    
    -- 查询
    explain select user_name, age from a where user_name like '%张';
    

在这里插入图片描述


九. 字符类型加引号

字符串类型字段作为搜索条件,不加引号会导致索引失效

MySQL的查询优化器,会自动进行类型的转换,比如下面的user_name会尝试转换为数字进行和1比较,造成索引失效

explain select user_name from a where user_name = '1';

explain select user_name from a where user_name = 1;

十. OR关键字前后索引问题

解决or前所索引失效:

  1. or前后字段是同一个并且有索引

    单独用一个字段进行or处理

    drop index idx_index on a;
    create index idx_index on a (user_name);
    
    explain select user_name, age from a where user_name = '张' or user_name = '张三';
    

    索引是没失效的

    在这里插入图片描述

  2. 使用in代替or

    create index idx_index on a (user_name);
    
    select * from a where user_name in ('1','2');
    
  3. 使用union

    • 创建索引

    • 查询语句修改

      select * from a where user_name = '张三' or city = '四川'
      

      改为

      select * from a where user_name = '张三' 
      union
      select * from a where  city = '四川'
      
    • 走了2个索引,创建个临时表,去重

      在这里插入图片描述

  4. 覆盖索引

    • 创建索引

      create index idx_index on a (user_name,city);
      
    • 查询

      explain select  user_name,city  from a where user_name = '张' or city = '四川';
      
    • 索引是没失效的
      在这里插入图片描述

如果前后字段不一,索引会失效么

如果像OR前后字段不一的话,即使创建索引,MySQL还是会走全表扫描,因为MySQL查询优化器难以同时用多个索引,只能用一个。利用OR的操作符,会导致MySQL无法利用单一索引进行有效的过滤。

create index idx_index on a (user_name);
create index idx_index2 on a (city);

explain select * from a where user_name = '张' or city = '四川';

索引是失效的

在这里插入图片描述


十一. 利用索引来做排序和分组

MySQL可以使用同一个索引既满足搜索,又满足于排序/分组,这样是最好的。

搜索条件和排序或分组顺序一致


十二. 升降序使用

对于联合索引进行排序的场景。我们要求各个排序列的顺序是一致的。要么都是ASC,要么都是DESC

如果排序多个列字段,不再一个索引里,这种情况不能使用索引排序,如:

index(a);
index(b);

order by a,b

十三. 按主键顺序插入

要避免随机的(不连续且分布范围非常大的)聚簇索引,特别是对I/O密集型的应用而言。列如,使用UUID当作聚簇索引,它会使插入变得完全随机,而且插入不仅花费很长时间(主键字段长),索引占据空间大(页分裂导致),数据没有任何聚集特性。

最简单的解决方案使用MySQL自带的AUTO_INCREMENT自增列,既保证了数据行的顺序写入,又根据主键做关联的操作的性能会更好。

主键值是顺序性的,所以InnoDB把每一条记录都存储在上一条记录后面。当达到页的最大填充因子时(InnoDB默认的最大填充因子是页大小的15/16,留出的1/16用与以后的修改),下一条记录就会写入新的页中。若数据按照这种顺序方式加载,主键页就会近似于被顺序记录填满,也正是所期望的结果。

若新行的主键值不一定比之前的大,带来什么结果?

InnoDB无法直接把新行插入到索引的最后,而是需要重新寻找合适的位置,通常是已有数据的中间位置,这样会增加额外的工作,导致数据分布不够优化。

写入的目标页可能已经刷到磁盘上并从缓存移除,或者还没加载到缓存中,InnoDB在插入前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的IO。

写入是乱序的,InnoDB不得不频繁的做页分裂操作,以便信的行分配空间。页分裂会导致移动大量数据,一次插入最少需要修改三个页而不是一个页。

所以使用InnoDB时应该尽可能安好主键顺序插入数据,并且尽可能的使用单调增加的聚簇键值进行插入新行。

十四. 优化count查询

count()是一个特殊的函数

  1. 可以统计某个列的数量(不统计NULL),如count(a)。
  2. 也可以统计行数,如count(1),count(*)

count()需要扫描大量行才能获取精确的数据,因此很难优化。在MySQL层面能做的就是索引覆盖扫描了,或者从下面的几个点做优化

  1. 需要考虑修改应用架构
  2. 增加汇总表
  3. Redis做缓存统计

十五. 优化limit分页

分页操作,对于业务系统是必不可少的。我们通常使用limit加上偏移量实现。

如果偏移量很大,会导致付出很大代价。如limit 10000,10,这时MySQL需要查询10010条数据记录后,返回10条,前面的1w条将抛弃,这样导致的成本很高。

⭐️解决方案一:

先查分页中需要的主键值,然后根据主键值回表查询所需记录,再次过程查询的数据通过id主键索引,效率会高一些。

explain select * from (select id from a limit 10000,10) b, a where a.id = b.id;

执行计划及过可看出,首先执行了子查询,根据主键做索引全表扫描,然后通过与a表通过id主键关联查询。相比传统写法效率会高一些。
在这里插入图片描述

⭐️解决方式二:

但解决方式一还是存在性能问题,最佳的方式是在业务进行配合修改

explain select * from a where id > 10000 order by id limit 10

采用这种写法,前端需通过点击More来获取更多数据,而不是纯粹的分页。因此,每次查询只需通过上次查询出的数据的id来获取接下来的数据即可,这种写法是需要配合业务。


十六. MySQL Null 说明

null的分歧:

  1. null是未确定的值,每个null都是独一无二
  2. 所有null就一个
  3. null无意义,不计入统计。

MySQL NULL说明

在MySQL,null有3个等级配置,通过innodb_stats_method配置:

  1. nulls_equal(默认配置):所有NULL值都是相等的,如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别多,所以倾向于不使用索引进行访问。
  2. nulls_unequal:认为所有NULL值不相等,如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别少,所以倾向于使用索引进行访问。
  3. nulls_ignored:忽略NULL值。
show VARIABLES like 'innodb_stats_method'

set global innodb_stats_method = 'nulls_equal'

而且有迹象表明,在MySQL5.7.22以后的版本,对这个innodb_stats_method的修改不起作用,MySQL把这个值在代码里写死为nulls_equal。也就是说MySQL在进行索引列的数据统计行为又把null视为第二种情况(NULL值在业务上就是代表没有,所有的NULL值和起来算一份),看起来,MySQL中对Null值的处理也很分裂。所以总的来说,对于列的声明尽可能的不要允许为null。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1964424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昇思25天学习打卡营第XX天|Pix2Pix实现图像转换

Pix2Pix是一种基于条件生成对抗网络&#xff08;cGAN&#xff09;的图像转换模型&#xff0c;由Isola等人在2017年提出。它能够实现多种图像到图像的转换任务&#xff0c;如从草图到彩色图像、从白天到夜晚的场景变换等。与传统专用机器学习方法不同&#xff0c;Pix2Pix提供了一…

【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式

这是【Dart 教程系列第 49 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.22.1、Dart SDK&#xff1a;3.4.1 文章目录 一&#xff1a;什么是策略设计模式&#xff1f;二&#xff1a;为什么要使用策略设计模式&#xff1…

pytest框架的作用--面试

在做接口自动化的时候我们经常会用到pytest这个框架&#xff0c;这个框架有哪些优点 1. 帮我们找到用例 模块名必须以test_开头&#xff1b;类名必须以Test开头&#xff0c;并且不能有init方法&#xff1b;用例方法必须以test开头 2.执行用例 有很多参数设置执行 3. 断言-可以…

大数据-51 Redis 高可用方案CAP-AP 主从复制 一主一从 全量和增量同步 哨兵模式 docker-compose测试

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

昇思25天学习打卡营第XX天|07-函数式自动微分

本章分为两类&#xff0c;一神经网络训练依赖反向传播算法&#xff0c;通过损失函数计算预测值与标签间的误差&#xff0c;反向传播求得梯度&#xff0c;进而更新模型参数。 二、自动微分简化了这一过程&#xff0c;将复杂运算分解为基础运算&#xff0c;隐藏了求导细节&#…

重新思考终端 LLMs 和 Agents

0x0 前言 LLM (Large Language Models) 的风头一时无两&#xff0c;席卷万千行业。业内不乏有关于 LLM 的研究和讨论&#xff0c;但鲜有立足终端的视角。团队上半年曾有过对 GPT 进终端的分析&#xff0c;但 LLM 日新月异&#xff0c;旧分析已经不完全跟得上变化了。适逢年底规…

达飞集团全新互联网投融资平台即将隆重上线

全球知名海运和物流巨头达飞集团(CMA CGM)即将推出其最新力作——全新互联网投融资平台。该平台旨在为投资者提供安全、稳定且高效的投资机会,通过筹集资金支持全球各地实体产业项目的落地建设。这一举措不仅展现了达飞集团在金融科技领域的创新能力,也彰显了其在海运和物流行业…

C# Solidworks二次开发------信息提示

一、内容 对于信息提示主要有两种方式。 方式一&#xff1a;Solidworks自带的提示 方式二&#xff1a;C#中的消息提示 二、代码 ISldWorks swApp Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application")) as SldWorks; swApp.NewPart(); ModelD…

懂个锤子Vue 自定义指定、插槽:

Vue自定义指定、插槽&#x1f6e0;️&#xff1a; 前言&#xff1a;当然既然学习框架的了&#xff0c;HTMLCSSJS三件套必须的就不说了&#xff1a; JavaScript 快速入门 紧跟前文&#xff0c;目标学习Vue2.0——3.0&#xff1a; 懂个锤子Vue、WebPack5.0、WebPack高级进阶 涉…

npm与webpack的学习笔记

npm 定义&#xff1a;npm是Node.js标准的软件包管理器。它起初是作为下载和管理Node.js包依赖的方式&#xff0c;但其现在也已成为前端JavaScript中使用的工具。 包 包&#xff1a;将模块、代码、其他资料聚合成一个文件夹 包的分类&#xff1a; 项目包&#xff1a;主要用…

upload-labs-靶场(1-19关详细解答 保姆级教程)

Pass-01 下载 upload-labs-mster靶场 创建一个upload目录 不然无法打开upload-labs 靶场 1.我们先上传一个 php后缀文件 显示我们该文件不允许上传 只能上传 jpg png gif的文件后缀 2.我们可以上传 自己写的php 后缀改成jpg上传 然后抓包 可以看到 数据上传了 我们可以在burps…

5.缓存雪崩问题及解决思路

缓存雪崩问题及解决思路 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 解决方案&#xff1a; 给不同的Key的TTL添加随机值利用Redis集群提高服务的可用性给缓存业务添加降级限流策略给业务添…

用EasyV全景图低成本重现真实场景,360°感受数字孪生

全景图&#xff0c;即借助绘画、相片、视频、三维模型等形式&#xff0c;通过广角的表现手段&#xff0c;尽可能多表现出周围的环境。避免了一般平面效果图视角单一&#xff0c;不能带来全方位视角的缺陷&#xff0c;能够全方位的展示360度球型范围内的所有景致&#xff0c;最大…

maven插件1(timer-plugin)

[https://maven.apache.org/plugin-developers/index.html(https://maven.apache.org/plugin-developers/index.html) 概述 timer plugin, 提供4个goal: currentTimecurrentDatecurrentMonthcurrentYear 打包 命令 maven clean install 常见错误 goalPrefix MISSING 错…

记录一次环境的安装

目录 新添加的代码 代码解释 为啥ubuntu用debian软件源 为啥修改sources.list.d S权限意思 php缺少和数据库连接的模块 使用root登陆数据库1698错误 字段解释 auth_socket解释 使用root登陆数据库方法 详细解释 首先在安装的时候&#xff0c;有一个dockerfile文件&a…

SpringBoot 依赖之Java Mail Sender邮件发送

Java Mail Sender 依赖名称: Java Mail Sender功能描述: Send email using Java Mail and Spring Framework’s JavaMailSender.使用 Java Mail 和 Spring Framework 的 JavaMailSender 发送电子邮件。 <dependency><groupId>org.springframework.boot</group…

机器学习模型选择与优化: 打造智能IDS

引言 面对琳琅满目的机器学习模型&#xff0c;你是否也感到无从下手&#xff1f;别担心&#xff0c;这篇文章将为你详细讲解各种模型的优缺点&#xff0c;帮助你选择最适合的模型&#xff0c;并进行优化。是时候给你的入侵检测系统装上最强“大脑”了! 机器学习模型概述 监…

java2~~~

类变量 [修饰符] class 类{[其他修饰符] static 数据类型 变量名; } static 用来修饰的结构&#xff1a;属性、方法; 代码块、内部类&#xff1b;&#xff08;构造器不能够用static修饰&#xff09;当类被加载进内存时&#xff0c;静态成员变量随着类一起进入方法区&#xff…

VMware安装Win10系统(保姆级教程)

需要自己先下载并安装VMware 和Win10系统镜像&#xff1a; VMware官网&#xff1a;VMware - Delivering a Digital Foundation For Businesses Win10下载地址&#xff1a;MSDN,我告诉你 1.新建虚拟机设置 2.启动Win10虚拟机设置 注意&#xff1a; 当出现有字体的时候&#…

“论云原生架构及其应用”写作框架软考高级论文系统架构设计师论文

论文真题 近年来&#xff0c;随着数字化转型不断深入&#xff0c;科技创新与业务发展不断融合&#xff0c;各行各业正在从大工业时代的固化范式进化成面向创新型组织与灵活型业务的崭新模式。在这一背景下&#xff0c;以容器和微服务架构为代表的云原生技术作为云计算服务的新…