【MySQL调优】如何进行MySQL调优?一篇文章就够了!

导航:

【Java笔记+踩坑汇总】Java基础+进阶+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud+黑马旅游+谷粒商城+学成在线+MySQL高级篇+设计模式+常见面试题+源码

目录

一、监控报警

二、排查慢SQL

2.1 开启慢查询日志 

2.2 找出最慢的几条SQL

2.3 分析查询计划 

三、MySQL调优

3.1 基础优化

3.1.1 缓存优化 

3.1.2 硬件优化

3.1.3 参数优化 

3.1.4 定期清理垃圾

3.1.5 使用合适的存储引擎

3.1.6 读写分离

3.1.7 分库分表

3.2 表设计优化

3.2.1 混合业务分表、冷热数据分表

3.2.2 联合查询改为中间关系表

3.2.3 遵循三个范式

3.2.4 字段建议非空约束

3.2.5 使用冗余字段

3.2.6 数据类型优化

3.3 索引优化

3.3.1 考虑索引失效的11个场景

3.3.2 遵循索引设计原则

3.3.3 连接查询优化

3.3.4 子查询优化

3.3.5 排序优化

3.3.6 分组优化

3.3.7 深分页查询优化

3.3.8 尽量覆盖索引

3.3.9 字符串前缀索引

3.3.10 尽量使用MySQL5.6支持的索引下推

3.3.11 写多读少的场景,尽量用普通索引

3.4 SQL优化


MySQL调优主要分为三个步骤:监控报警、 排查慢SQL、MySQL调优。

一、监控报警

监控工具(例如Prometheus+Grafana)监控MySQL,发现查询性能变慢,报警提醒运维人员:

二、排查慢SQL

2.1 开启慢查询日志 

查看慢查询次数:

show status like 'slow_queries';

开启慢查询日志,修改慢查询阈值:

set slow_query_log='ON';    #开启慢查询日志
set long_query_time = 1;     #设置慢查询阈值

2.2 找出最慢的几条SQL

慢查询日志分析工具mysqldumpslow找到最慢的几条语句:

mysqldumpslow 命令的具体参数如下:

  • -a: 不将数字抽象成N,字符串抽象成S
  • -s: 是表示按照何种方式排序:
  • c: 访问次数
  • l: 锁定时间
  • r: 返回记录
  • t: 查询时间
  • al:平均锁定时间
  • ar:平均返回记录数
  • at:平均查询时间 (默认方式)
  • ac:平均查询次数
  • -t: 即为返回前面多少条的数据;
  • -g: 后边搭配一个正则匹配模式,大小写不敏感的;

示例: 按照查询时间排序,查看前五条 慢查询SQL 语句

#命令行,按照查询时间排序,查看前五条 慢查询SQL 语句
mysqldumpslow -s t -t 5 /var/lib/mysql/xxx-slow.log    

2.3 分析查询计划 

explan分析sql执行计划(访问类型、记录条数、索引长度等);主要关注字段:

  1. possible_keys:查询可能用到的索引
  2. key:实际使用的索引
  3. key_len:实际使用的索引的字节数长度。
  4. type:访问类型,看有没有走索引。all(全表扫描),ref(命中非唯一索引),const(命中主键/唯一索引)、range(范围索引查询)、index_merge(使用多个索引)、 system(一行记录时,快速查询)。
  5. Extra:额外信息。看有没有走索引。
    1. using index:覆盖索引,不回表。
    2. using filesort:需要额外的排序。排序分为索引排序和filesort排序,索引排序一般更快,深分页等查询数据量大时filesort更快。
    3. using index condition:索引下推。MySQL5.6开始支持。联合索引某字段是模糊查询(非左模糊)时,该字段进行条件判断后,后面几个字段可以直接条件判断,判断过滤后再回表对不包含在联合索引内的字段条件进行判断。
    4. using where:不走索引,全表扫描。

执行计划各个列的作用

id 每个SELECT子句或者join操作都会被分配一个唯一的编号,编号越小优先级越高,id相同的语句可以被认为是一组。id为NULL表示独立的子查询,子查询优先级都比主查询高。
select_type 查询的类型。主查询(primary)、普通查询(simple)、联合查询、子查询(subquery)、derived(from表临时子查询)、union(union后查询)、union result()
table 表名。显示当前这行的数据是哪个表的。
partitions 匹配的分区信息。如果表未分区则为NULL。
type 访问类型,根据索引、全表扫描等方法来执行查询的优化策略。all(全表扫描),ref(命中非唯一索引),const(命中主键/唯一索引)、range(范围索引查询)、index_merge(使用多个索引)、 system(一行记录时,快速查询)。
possible_keys

可能用到的索引。列出MySQL能够使用哪些索引来查询。

如果该列只有一个possible_keys,通常意味着这个查询是高效的。

如果这个列有多个possible_keys,并且MySQL只使用了其中一个,则需要考虑是否需要在该列上增加一个联合索引。

key 实际上使用的索引。如果没有明确的指定KEY,MySQL会根据查询条件自动选择最优的索引。
key_len 实际使用到索引的字节数长度。越短表示越快,一般表示索引字段越小越好。
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息。常量等值查询const, 表达式/函数使用到时func,关联查询显示关联字段名
rows 预估的需要读取的记录条数。数值越小越好,表示结果集越小,查询越高效。
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比。这个值越小越好,说明可通过索引直接返回数据。
Extra 额外信息。看有没有走索引,还是全表扫描了。一般搭配type字段看。Using index(使用到覆盖索引)、Using where(未使用索引查询)、Using temporary(临时表存储结果集.排序/分组会使用)、Using filesort(排序操作未用索引)、Using join buffer(连接条件未用索引)、Impossible where(where约束语句可能有问题导致没有结果集)

三、MySQL调优

3.1 基础优化

3.1.1 缓存优化 

mysql调整缓冲池大小等参数;引入redis。tip:InnoDB使用缓冲池缓存记录和索引

3.1.2 硬件优化

服务器加内存条、升级SSD固态硬盘、把磁盘I/O分散在多个设备、配置多处理器。

3.1.3 参数优化 

关闭不必要的服务和日志:调优结束关闭慢查询日志;

调整最大连接数:max_connections ;

线程池缓存线程数:thread_cache_size,缓存空闲线程,有连接时直接分配该线程处理连接;

缓冲池大小:innodb_buffer_pool_size 。

3.1.4 定期清理垃圾

对于不再使用的表、数据、日志、缓存等,应该及时清理,避免占用过多的MySQL资源,从而提高MySQL的性能。

3.1.5 使用合适的存储引擎

MyISAM:适合读取频繁,写入较少的场景(因为表级锁、B+树叶存地址)

InnoDB:适合并发写入的场景(因为行级锁、B+树叶存记录)。

InnoDB:支持外键和事务,行锁适合高并发,缓存索引和数据,内存要求高(因为要缓存索引和记录),适合存大数据量,增删改性能更优(行级锁高并发),耗费磁盘(因为有多个非聚簇索引,索引可能比记录空间还大)。

MyISAM:不支持外键和事务,表锁不适合高并发,缓存索引和数据地址,内存要求低(因为不用缓存记录),查询性能更优(因为查询时InnoDB要维护MVCC一致,而且多缓存了记录),节省磁盘(因为磁盘不存完整记录)。

对比

InnoDB

MyISAM

特点

支持外键和事务

不支持外键和事务

行表锁

行锁,操作时只锁某一行,不对其它行有影响, 适合高并发的操作

表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

缓存

缓存索引和数据,对内存要求较高,而且内存大小对性能有决定性的影响

只缓存索引,不缓存真实数据

关注点

事务:并发写、事务、更大资源

性能:节省资源、消耗少、简单业务、查询快

默认使用

5.5及其之后

5.5之前

3.1.6 读写分离

读写分离读写分离能有效提高查询性能。主从同步用到bin log和relay log。

3.1.7 分库分表

分库分表:数据量级到达千万级以上后,垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)。

概念:

  • 只分表:单表数据量大,读写出现瓶颈,这个表所在的库还可以支撑未来几年的增长。
  • 只分库:整个数据库读写出现性能瓶颈,将整个库拆开。
  • 分库分表:单表数据量大,所在库也出现性能瓶颈,就要既分库又分表。
  • 垂直拆分:把字段分开。例如spu表的pic字段特别长,建议把这个pic字段拆到另一个表(同库或不同库)。
  • 水平拆分:把记录分开。例如表数据量到达百万,我们拆成四张20万的表。

 拆分原则:

数据量增长情况 数据表类型 优化核心思想
数据量为千万级,是一个相对稳定的数据量 状态表 能不拆就不拆读需求水平扩展
数据量为千万级,可能达到亿级或者更高 流水表 业务拆分,面向分布式存储设计
数据量为千万级,可能达到亿级或者更高 流水表 设计数据统计需求存储的分布式扩展
数据量为千万级,不应该有这么多的数据 配置表 小而简,避免大一统

分库分表步骤:

  1. MySQL调优:数据量能稳定在千万级,近几年不会到达亿级,其实是不用着急拆的,先尝试MySQL调优,优化读写性能。

  2. 目标评估:评估拆几个库、表,举例: 当前20亿,5年后评估为100亿。分几个表? 分几个库?解答:一个合理的答案,1024个表,16个库按1024个表算,拆分完单表200万,5年后为1000万.1024个表*200w≈100亿

  3. 表拆分:

    • 业务层拆分:混合业务拆分为独立业务、冷热分离

    • 数据层拆分:

      • 按日期拆分:这种使用方式比较普遍,尤其是按照日期维度的拆分,其实在程序层面的改动很小,但是扩展性方面的收益很大。

        • 日维度拆分,如test_20191021
        • 月维度拆分,如test_201910
        • 年维度拆分,如test_2019
      • 按主键范围拆分:例如【1,200w】主键在一个表,【200w,400w】主键在一个表。优点是单表数据量可控。缺点是流量无法分摊,写操作集中在最后面的表。

      • 中间表映射:表随意拆分,引入中间表记录查询的字段值,以及它对应的数据在哪个表里。优点是灵活。确定是引入中间表让流程变复杂。

      • hash切分:sharding_key%N。优点是数据分片均匀,流量分摊。缺点是扩容需要迁移数据,跨节点查询问题。

      • 按分区拆分:hash,range等方式。不建议,因为数据其实难以实现水平扩展。

  4. sharding_key(分表字段)选择:尽量选择查询频率最高的字段,然后根据表拆分方式选择字段。

  5. 代码改造:修改代码里的查询、更新语句,以便让其适应分库分表后的情况。

  6. 数据迁移:最简单的就是停机迁移,复杂点的就是不停机迁移,要考虑增量同步和全量同步的问题。

    1. 全量同步:老库到新库的数据迁移,要控制好迁移效率,解决增量数据的一致性。

      1. 定时任务:定时任务查老库写新库
      2. 中间件:使用中间件迁移数据
    2. 增量同步:老库迁移到新库期间,新增删改命令的落库不能出错

      1. 同步双写:同步写新库和老库;
      2. 异步双写(推荐): 写老库,监听binlog异步同步到新库
      3. 中间件同步工具:通过一定的规则将数据同步到目标库表
  7. 数据一致性校验和补偿:假设采用异步双写方案,在迁移完成后,逐条对比新老库数据,一致则跳过,不一致则补偿:

    1. 新库存在,老库不存在:新库删除数据
    2. 新库不存在,老库存在:新库插入数据
    3. 新库存在、老库存在:比较所有字段,不一致则将新库更新为老库数据
  8. 灰度切读:灰度发布指黑(旧版本)与白(新版本)之间,让一些用户继续用旧版本,一些用户开始用新版本,如果用户对新版本没什么意见,就逐步把所有用户迁移到新版本,实现平滑过渡发布。原则:

    1. 有问题及时切回老库
    2. 灰度放量先慢后快,每次放量观察一段时间
    3. 支持灵活的规则:门店维度灰度、百 (万)分比灰度
  9. 停老用新:下线老库,用新库读写。

3.2 表设计优化

3.2.1 混合业务分表、冷热数据分表

例如把一个大的任务表,分离成任务表和历史任务表,任务表里任务完成后移动到历史任务表。任务表是热数据,历史任务表是冷数据,提高查询性能。

3.2.2 联合查询改为中间关系表

例如属性表和属性分组表,不使用连接查询,使用“属性-属性分组表”存储每条属性与“属性关系”的id。

3.2.3 遵循三个范式

每个属性不可再分、表必须有且只有一个主键、非主键列必须直接依赖于主键

3.2.4 字段建议非空约束

①可能查询出现空指针问题;

②导致聚合函数不准确,因为它会忽略null

③不能用“=”判断,只能用is null判断;

④null和其他值运算只能是null,可能让你不小心把它当成0;

⑤null值比空字符更占用空间,空值长度是0,null长度是1bit;

⑥不覆盖索引情况下,is not null无法用索引

3.2.5 使用冗余字段

虽然列字段不能太多,但为查询效率可增加冗余字段

3.2.6 数据类型优化

 整数类型:

考虑好数值范围,前期可以使用int保证稳定性。非负数类型要用UNSIGNED;同样字节数,存储的数值范围更大。主键一般使用bigint,布尔类型tinint

能整数就不要用文本类型:

跟文本类型数据相比,大整数往往占用更少的存储空间。

避免使用TEXT、BLOB数据类:

这两个大数据类型,排序时不能使用临时内存表,只能使用磁盘临时表,效率很差,建议别用,或分表到单独扩展表里。LongBlob类型能存储4G文件;

避免使用枚举类型:

排序很慢。

使用TIMESTAMP存储时间:

TIMESTAMP使用4字节,DATETIME使用8个字节,同时TIMESTAMP具有自动赋值以及自动更新的特性。 缺点是只能存到2038年,MySQL5.6.4版本可以参数配置,自动修改它为BIGINT类型。

DECIMAL存浮点数:

Decimal类型为精准浮点数,在计算时不会丢失精度,尤其是财务相关的金融类数据。占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节。可用于存储比bigint更大的整型数据。 

3.3 索引优化

3.3.1 考虑索引失效的11个场景

详细请参考:

MySQL高级篇——索引失效的11种情况_vincewm的博客-CSDN博客

 尽量全值匹配:

查询age and classId and name时,(age,classId,name)索引比(age,classId)快。

考虑最左前缀:

联合索引把频繁查询的列放左。索引(a,b,c),只能查(a,b,c),(a,b),(a)。

主键尽量有序:

如果主键不有序,需要查找目标位置再插入,并且如果目标位置所在数据页满了就必须得分页,造成性能损耗。可以选择自增策略或MySQL8.0有序UUID策略。

计算、函数导致索引失效:

计算例如where num+1=2,函数例如abs(num)取绝对值

类型转换导致索引失效:

例如name=123,而不是name='123'。又例如使用了不同字符集。

范围条件右边的列索引失效:

例如(a,b,c)联合索引,查询条件a,b,c,如果b使用了范围查询,那么b右边的c索引失效。建议把需要范围查询的字段放在最后。范围包括:(<) (<=) (>) (>=) 和 between。

没覆盖索引时,“不等于”导致索引失效:

因为“不等于”不能精准匹配,全表扫描二级索引树再回表效率不如直接全表扫描聚簇索引树。但使用覆盖索引时,联合索引数据量小,加载到内存所需空间比聚簇索引树小,且不需要回表,索引效率优于全表扫描聚簇索引树。

覆盖索引:一个索引包含了满足查询结果的数据就叫做覆盖索引,不需要回表等操作。

没覆盖索引时,左模糊查询导致索引失效:

例如LIKE '%abc'。因为字符串开头都不能精准匹配。跟上面一个道理。

没覆盖索引时,is not null、not like无法使用索引:

因为不能精准匹配。跟上面一个道理。

“OR”前后存在非索引列,导致索引失效:

MySQL里,即使or左边条件满足,右边条件依然要进行判断。

不同字符集导致索引失败:

建议utf8mb4,不同的字符集进行比较前需要进行 转换 会造成索引失效。 

3.3.2 遵循索引设计原则

 详细请参考:

MySQL高级篇——索引的创建与设计原则_vincewm的博客-CSDN博客

  1.  命名:索引的字段个数尽量别超过5个,命名格式“idx_col1_col2”
  2. 在频繁查询(特别是分组、范围、排序查询)的列建立索引;
  3. 频繁更新的表,不要创建过多索引
  4. 唯一特性的字段,适合创建索引;
  5. 很长的varchar字段,适合根据区分度和长度创建前缀索引;
  6. 多个字段都要创建索引时,联合索引优于单值索引;
  7. 避免创建过多索引,避免索引失效;
  8. 尽量用有序的字段作为主键索引:防止乱序时新主键前移到已满的数据页,导致插入后分裂数据页,造成性能损耗; 

3.3.3 连接查询优化

详细请参考:

MySQL高级篇——索引失效的11种情况_vincewm的博客-CSDN博客

外连 接时优先给被驱动表连接字段加索引:

外连接查询时,右表就是被驱动表,建议加索引。因为左表是查所有数据,右表是按条件查询,所以右表的条件字段创建索引价值更高一点。

内连接时优化器自动非索引驱动索引表、小表驱动大表:

先优先选有索引的表做被驱动表。两个表都没有索引时,查询优化器会自动让小表驱动大表。被驱动表的JOIN字段创建索引会极大地提高查询效率。

两表连接字段 类型必须一致:

两个表JOIN字段数据类型保持绝对一致。防止自动类型转换导致索引失效。

3.3.4 子查询优化

详细请参考:

MySQL高级篇——索引失效的11种情况_vincewm的博客-CSDN博客

关联替代子查询:能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)。子查询是一个SELECT查询的结果作为另一个SELECT语句的条件。

#取所有不为班长的同学
SELECT a.* FROM student a WHERE a.stuno NOT IN (
    SELECT monitor FROM class b
    WHERE monitor IS NOT NULL
);
#优化成关联查询
SELECT a.* FROM student a LEFT OUTER JOIN class b 
ON a.stuno = b.monitor WHERE b.monitor IS NULL;

多次查询代替子查询:不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。

3.3.5 排序优化

详细请参考:

MySQL高级篇——排序、分组、分页优化_vincewm的博客-CSDN博客

  • 优化器自动选择排序方式:MySQL支持索引排序和FileSort排序,索引保证记录有序性,性能高,推荐使用。FileSort排序是内存中排序,数据量大时产生临时文件在磁盘里排序,效率低还占用大量CPU。并不是说FileSort一定效率低,一些情况它可能效率高。例如没覆盖索引的左模糊、“不等于”、not null等索引失效情况下,全表扫描效率比非聚簇索引树遍历再回表更高。
  • 要符合最左前缀:where后条件和order by字段创建联合索引,顺序要需要符合最左前缀。例如索引(a,b,c),查询where a=1 order by b,c。
  • 要么全升序要么全降序:排序顺序必须要么全部DESC,要么全部ASC。乱序会导致索引失效。
  • 待排序数量大时,尽管索引没失效,索引效率不如filesort:待排序数据量大约超过一万个,就不走索引走filesort了。建议用limit和where过滤,减少数据量。数据量很大时,索引排序完需要回表查所有数据,性能很差,还不如FileSort在内存中排序效率高。并不是说使用limit一定会走索引排序,关键看的是数据量,数据量过大时优化器会使用FileSort排序。
  • 范围查询右边排序索引失效:例如索引(a,b,c),查询where a>1 order by b,c,导致b,c排序不能走索引,需要filesort。
  • 范围查询过滤量大时,优先范围字段加索引:当【范围条件】和【group by 或者 order by】的字段出现二选一时,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。这样即使范围查询导致排序索引失效,效率依然比只索引排序字段时候高。如果只能过滤一点点,那就优先索引放到排序字段上。
  • 调优FileSort :无法使用 Index 排序时,需要对 FileSort 方式进行调优。例如增大sort_buffer_size(排序缓冲区大小)和 max_length_for_sort_data(排序数据最大长度)

3.3.6 分组优化

跟排序基本一个思路。

排序分组都比较耗费cpu,能不用就不用。

where效率高于having。where是分组前过滤,having是分组后过滤。

3.3.7 深分页查询优化

需求是返回第2000000~2000010 的记录

 主键有序的表根据主键排序,先过滤再排序:直接查范围之后的几个数据。

EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;

 主键不有序的表根据主键排序,先给主键分页,然后内连接原表:当前表内连接排序截取后的主键表,连接字段是主键。因为查主键是在聚簇索引树查,不用回表,排序和分页很快

EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10) a WHERE t.id = a.id;

 主键有序的表根据非主键排序:得到上一页最后一条记录x,那么目标页码的所有记录id都比x.id小(因为逆序,且排序依据其实是age,id,主键自增),目标页码的所有记录age都比x.age小或等于。

EXPLAIN SELECT * FROM student WHERE id<#{x.id} AND age>=#{x.age} ORDER BY age DESC LIMIT 10;

3.3.8 尽量覆盖索引

详细请参考:

MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计_vincewm的博客-CSDN博客

一个索引包含了满足查询结果的数据。因为不需要回表,所以查询效率高。覆盖索引时“左模糊”和“不等于”不能让索引失效。

示例:

#没覆盖索引的情况下,左模糊查询导致索引失效
CREATE INDEX idx_age_name ON student(age, NAME);
EXPLAIN SELECT * FROM student WHERE NAME LIKE '%abc';

覆盖索引:一个索引包含了满足查询结果的数据就叫做覆盖索引,不需要回表等操作。

索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。

覆盖索引是非聚簇索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列 (即建索引的字段正好是覆盖查询条件中所涉及的字段)。简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列 。

3.3.9 字符串前缀索引

例如(email(6)),给字符串前缀而不是整个字符串添加索引,前缀长度要根据区分度和长度进行取舍。

示例:

MySQL是支持前缀索引的。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。

mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));

这两种不同的定义在数据结构和存储上有什么区别呢?下图就是这两个索引的示意图。

如果使用的是index1(索引包含整个字符串),执行顺序是这样的:

  1. 从index1索引树找到满足索引值是’ [email protected]’的这条记录,取得ID2的值;
  2. 回表到主键上查到主键值是ID2的行,判断email的值是正确的,将这行记录加入结果集;
  3. 取index1索引树上刚刚查到的位置的下一条记录,发现已经不满足email=' [email protected] ’的 条件了,循环结束。

这个过程中,只需要回主键索引取一次数据,所以系统认为只扫描了一行。

如果使用的是index2(索引包含字符串前缀email(6)),执行顺序是这样的:

  1. 从index2索引树找到满足索引值是’zhangs’的记录,找到的第一个是ID1;
  2. 回表到主键上查到主键值是ID1的行,判断出email的值不是’ [email protected] ’,这行记录丢弃;
  3. 取index2上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出ID2,再到回表到ID索引上取整行然后判断,这次值对了,将这行记录加入结果集;
  4. 重复上一步,直到在index2上取到的值不是’zhangs’时,循环结束。

也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。前面 已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。

3.3.10 尽量使用MySQL5.6支持的索引下推

联合索引某字段是模糊查询(非左模糊)时,该字段进行条件判断后,后面几个字段可以直接条件判断,判断过滤后再回表对不包含在联合索引内的字段条件进行判断。

例如索引(name,age),查询name like 'z%' and age and address,模糊查询导致age无序。

在联合索引树查询时不止查name,还会判断后面的age,过滤后再回表判断address。而如果关闭了索引下推,联合索引里模糊查询(非左)后面几个字段不能在联合索引树里直接条件判断,必须回表后再判断。

详解:

索引下推(ICP,Index Condition Pushdown)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。

  • 如果没有ICP:联合索引某字段是模糊查询(非左模糊)时,该字段进行条件判断后,后面几个字段不能用来直接条件判断,必须回表后再判断。
  • 启用ICP 后:联合索引某字段是模糊查询(非左模糊)时,该字段进行条件判断后,后面几个字段可以直接条件判断,判断过滤后再回表对不包含在联合索引内的字段条件进行判断。主要优化点是在回表之前过滤,减少回表次数。主要应用:模糊查询(非左模糊)导致索引里该字段后面的字段无序,必须要回表判断,而使用了索引下推,就不需要回表,直接在联合索引树里判断。

如果没有ICP ,存储引擎会遍历索引以定位基表中的行,并将它们返回给 MySQL 服务器,由 MySQL 服务器评古WHERE 后面的条件是否保留行。
启用ICP 后,如果部分 WHERE 条件可以仅使用索引中的列进行筛选,则MySQL 服务器会把这部分WHERE 条件放到存储引擎筛选。然后,存储引擎通过使用索引条目来筛选数据,并且只有在满足这一条件时才从表中读取行。

好处: ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。但是,ICP的 加速效果 取决于在存储引擎内通过 ICP筛选 的数据的比例。 

举例:

不支持索引下推的联合索引:例如索引(name,age),查询name like 'z%' and age=?,模糊查询导致age无序。在联合索引树查询时只会查name,后面的age乱序不能直接进行条件判断,必须回表后再判断age。

而支持索引下推的联合索引:例如索引(name,age),查询name like 'z%' and age and address,在联合索引树查询时不止查name,还会判断后面的age,过滤后再回表判断address。

CREATE INDEX idx_name_age ON student(name,age);
#索引失败;非覆盖索引时,左模糊导致索引失效
EXPLAIN SELECT * FROM student WHERE name like '%bc%' AND age=30;
#索引成功;MySQL5.6引入索引下推,where后面的name和age都在联合索引里,可以又过滤又索引,不用回表,索引生效
EXPLAIN SELECT * FROM student WHERE `name` like 'bc%' AND age=30;
#索引成功;name走索引,age用到索引下推过滤,classid不在联合索引里,需要回表。
EXPLAIN SELECT * FROM student WHERE `name` like 'bc%' AND age=30 AND classid=2;

好处: 某些场景下ICP可以大大减少回表次数,提高性能。ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。但是,ICP的 加速效果 取决于在存储引擎内通过 ICP筛选 的数据的比例。

3.3.11 写多读少的场景,尽量用普通索引

查询时普通索引和唯一索引效率差不多;更新时普通索引效率更高,因为有change buffer(写缓存)将更新后的数据页缓存到内存,下次访问时或后台定期会执行merge操作,将该数据页写入磁盘。

change buffer在事务提交时会写入redo log,保证数据持久化。普通索引:不加任何限制条件,如create index idx_name on student(name)。唯一索引:UNIQUE参数限制索引唯一,如create UNIQUE index idx_name on student(name)。

详解: 

写缓存(change buffer):

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话, 在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

merge :将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge 。除了访问这个数据页会触发merge外,系统有后台线程会定期merge。在数据库正常关闭(shutdown)的过程中,也会执行merge 操作。

如果能够将更新操作先记录在change buffer, 减少读磁盘 ,语句的执行速度会得到明显的提升。而且, 数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存,提高内存利用率。

唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用。

做好区分:

  • 读数据用的是缓冲池buffer pool
  • 重做日志有个redo log buffer,是将缓冲池里更新的数据写入redo log buffer,事务提交时根据刷盘策略,将redo log buffer刷盘到redo log file或page cache。

3.4 SQL优化

详细请参考:

MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计_vincewm的博客-CSDN博客

 合理选用EXISTS 和 IN :

遵循小表驱动大表原则,左边表小就是EXISTS,左边表大就用IN。

尽量COUNT(1)或COUNT(*):

innoDB时,COUNT(1),COUNT(*)时,查询优化器会优先选用有索引的、占用空间最小的二级索引树进行统计,只有找不到非聚簇索引树时采用使用聚簇索引树统计,空间占用大。当然也能COUNT(最小空间二级索引字段),但麻烦不如交给优化器自动选择。MyISAM时,就无所谓了,用哪个时间复杂度都是O(1)。

尽量SELECT(明确字段):

建议明确字段,查询优化器解析“*”符号为所有列名耗费时间,并且“*”号无法使用覆盖索引。

全表扫描时尽量用“LIMIT”:

当全表扫描时,并且你知道结果集记录数量时,用limit限制,这样扫描足够数量后就停止,不再扫描完全表了。如果有索引,就无需用limit了。

使用limit N,少用limit M,N:

特别是大表或M比较大的时候。

将长事务拆为多个小事务:

尽量多使用 COMMIT,用编程式事务而不是声明式事务,降低事务粒度。提交事务可以释放的资源:回滚段上用于恢复数据的信息、锁、redo / undo log buffer 中的空间。

先查再删改:

UPDATE、DELETE语句一定要有明确的WHERE条件。

尽量UNION ALL而不是UNION:

UNION A LL不去重,速度更快。

猜你喜欢

转载自blog.csdn.net/qq_40991313/article/details/131059110