面试笔记——MySQL(优化篇:定位慢查询、SQL执行计划、索引、SQL优化)

定位慢查询

在MySQL应用中,慢查询 通常指的是执行时间超过一定阈值的查询语句。这个阈值通常由管理员或开发人员根据具体情况设置,一般是以毫秒为单位。慢查询可能会影响系统性能和用户体验,因此需要及时识别和优化。
表象: 页面加载过慢、接口压测响应时间过长(超过1s)

以下情景可能会导致慢查询:

  • 聚合查询
  • 多表查询
  • 表数据量过大查询
  • 深度分页查询
定位慢查询的方法

方案一:工具

调试工具:Arthas;

运维工具:Prometheus、Skywalking;
方案二:MySQL自带慢日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

复制代码
# 开启MySQL慢日志查询开关,值为0表示不开启慢日志查询
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log,如图:

注意:使用慢日志会损失一部分性能,因此一般是在调试阶段开启慢日志的功能,在生产环境下关闭。

SQL执行计划

可以采用EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,语法:

复制代码
# 直接在select语句前加上关键字explain/desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;
  • possible_keys 当前sql可能会使用到的索引
  • key 当前sql实际命中的索引
  • key_len 索引占用的大小(ps:可以通过key和key_len查看是否可能会命中索引。)
  • Extra 额外的优化建议,比如某个索引在使用的过程中是否使用了回表,下图中展示了两个参考信息:
  • type 这条sql的连接的类型,性能由好到差为NULL、system、const、eq_ref、ref、range、 index、all
    • system:表示当前语句查询的表是MySQL系统中内置的表,(不常用);
    • const:根据主键查询,(使用频率高);
    • eq_ref:主键索引查询或唯一索引查询,只能返回一条数据;
    • ref:索引查询,查询结果可能是多条数据;
    • range:范围查询,(应用中的最低要求);
    • index:索引树扫描;
    • all:全盘扫描;
    • 若type的值是index或all,则说明此条SQL语句需要优化。

索引

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库还维持着满足特定查找算法的数据结构(B+树),这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种结构就是索引。下图展示了以二叉树为索引,查找年龄为20的结点过程:

B-Tree(B树) 是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。

以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key:

B+Tree 是在BTree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。如图所示:

在B+树中,非叶子结点只存储指针不存储数据,数据存储在叶子节点中。

B树与B+树对比:

  1. 磁盘读写代价B+树更低(因为非叶子结点不存储任何的数据,它们只用存储指针,相对来说存储压力更低);
  2. 查询效率B+树更加稳定(查找时,最终都是从叶节点获取数据,因此效率比较稳定);
  3. B+树便于扫库和区间查询(叶节点之间通过双向指针进行连接,在进行范围查询时更加方便)。

索引的基本概念问题

  1. 什么是索引
    • 索引(index)是帮助MySQL高效获取数据的数据结构(有序)
    • 提高数据检索的效率,降低数据库的IO成本(不需要全表扫描)
    • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
  2. 索引的底层数据结构是什么?
    • MySQL的InnoDB引擎采用的B+树的数据结构来存储索引
    • 阶数更多,路径更短
    • 磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据
    • B+树便于扫库和区间查询,叶子节点是一个双向链表
聚集索引与二级索引(非聚集索引)
分类 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

如下图所示,注意------聚集索引的叶节点存储的是整行数据,二级索引的叶节点存储的是数据的主键值,在:

回表查询

回表查询:通过二级索引找到对应的主键值,到聚集索引中查找整行数据,这个过程就是回表。如图所示:

回表查询可能会影响查询性能,因为它需要额外的IO操作和访问磁盘,尤其是在大型表或者索引列宽度较大的情况下。

覆盖索引

覆盖索引 是指一个索引包含了查询语句所需的所有列,从而使得查询可以直接从索引中获取所需的数据,而无需回表查询。覆盖索引通常用于查询语句的列与索引列完全匹配的情况。如图:

查询语句:select * from tb_user where id = 2 ;

查询语句:select id, name from tb_user where name = 'Arm' ;

查询语句:select id,name,gender from tb_user where name = 'Arm' ;

使用覆盖索引处理MySQL超大分页:

在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。

我们一起来看看执行limit分页查询耗时对比:

因为,当在进行分页查询时,如果执行 limit 9000000,10 ,此时需要MySQL排序前9000010 记录,仅仅返回 9000000 - 9000010 的记录,其他记录丢弃,查询排序的代价非常大 。

解决方案:覆盖索引 + 子查询

复制代码
select *
from tb_sku t,
     (select id from tb_sku order by id limit 9000000,10) a
where t.id = a.id;

在执行select id from tb_sku order by id limit 9000000,10时,可以使用覆盖索引,性能更高;然后与之前的表做等价查询。

创建索引的原则

1). 针对于数据量较大 (基本条件),且查询比较频繁的表建立索引。单表超过10万数据(增加用户体验)
2). 针对于常作为查询条件 (where)、排序(order by)、分组(group by)操作的字段建立索引。

3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高 ,使用索引的效率越高。

4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
5). 尽量使用联合索引 ,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
6).控制索引的数量 ,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

索引失效

索引失效指的是数据库查询执行时,无法有效利用现有索引进行查询优化,从而导致性能下降或无法利用索引的情况。

以下情况会导致索引失效:

  1. 违反最左前缀法则

    • 最左前缀法则(或最左前缀匹配)指的是当一个查询包含了多列的复合索引时,查询条件查询从索引的最左前列开始,并且不跳过索引中的列。

    • 举例说明,假设有一个复合索引 (col1, col2, col3),那么以下查询可以利用该索引进行查询优化:

      • WHERE col1 = 'value'
      • WHERE col1 = 'value' AND col2 = 'value2'
      • WHERE col1 = 'value' AND col2 = 'value2' AND col3 = 'value3'

      例如,当前有表tb_seller,它的索引情况为:

      情况一 ------违法最左前缀法则 , 索引失效:

      情况二 ------如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效(图中只有status索引生效):

  2. 范围查询右边的列,不能使用索引

    同上例,当status使用范围查询时,address是失效的:

    查询结果与只用name和status索引的效果一样:

  3. 不要在索引列上进行运算操作, 索引将失效

    同例1,若对name进行了运算操作后,索引就失效了:

  4. 字符串不加单引号,造成索引失效

    同例1,第2条查询语句未加单引号, MySQL的查询优化器,会自动的进行类型转换,造成索引失效:

  5. 以%开头的Like模糊查询,索引失效。 如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效

    同例1,

SQL优化

表的设计优化

关于类型的选择------需要根据存储的内容来选择合适的类型,如:

  • 设置合适的数值(tinyint int bigint),
  • 设置合适的字符串类型(char和varchar)char定长效率高,varchar可变长度,效率稍低
索引优化

参考上文------索引优化创建原则和索引失效

SQL语句优化
  • SELECT语句务必指明字段名称(避免直接使用select * ,从而避免回表查询)
  • SQL语句要避免造成索引失效的写法
  • 尽量用union all代替union union会多一次过滤,效率低
  • 避免在where子句中对字段进行表达式操作
  • Join优化:能用inner join 就不用left join, right join,如必须使用 一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外边,把大表放到里边。left join 或 right join,不会重新调整顺序
    • 以小表为驱动:在执行连接操作时,应该将较小的表作为驱动表(驱动表是在连接操作中被选择的第一个表),这样可以减少处理的数据量,提高查询性能。
    • 内连接的优化:内连接会对两个表进行优化,因为它只返回两个表中匹配的行,而不需要额外的处理。所以,如果使用 INNER JOIN,推荐将较小的表放在外层,因为外层的表会优先进行匹配。
    • left join 或 right join 不会重新调整顺序:LEFT JOIN 和 RIGHT JOIN 会按照查询中指定的顺序进行操作,并不会重新调整表的顺序。因此,如果使用 LEFT JOIN 或 RIGHT JOIN,需要明确考虑表的顺序,以确保查询结果符合预期。
主从复制、读写分离

如果数据库的使用场景读的操作比较多的时候,为了避免写的操作所造成的性能影响 可以采用读写分离的架构。

读写分离解决的是,数据库的写入对数据查询的影响

分库分表

当数据量很大(例如超过五百万)的时候,可以采取分库分表优化。

面试笔记------MySQL(主从同步原理、分库分表)

相关推荐
叁沐8 分钟前
MySQL 05 深入浅出索引(下)
mysql
陈卓41040 分钟前
MySQL-主从复制&分库分表
android·mysql·adb
牛客企业服务40 分钟前
2025年AI面试推荐榜单,数字化招聘转型优选
人工智能·python·算法·面试·职场和发展·金融·求职招聘
你都会上树?1 小时前
MySQL MVCC 详解
数据库·mysql
笑衬人心。2 小时前
Ubuntu 22.04 修改默认 Python 版本为 Python3 笔记
笔记·python·ubuntu
长征coder2 小时前
AWS MySQL 读写分离配置指南
mysql·云计算·aws
Penk是个码农2 小时前
web前端面试-- MVC、MVP、MVVM 架构模式对比
前端·面试·mvc
MrSkye2 小时前
🔥JavaScript 入门必知:代码如何运行、变量提升与 let/const🔥
前端·javascript·面试
爱学习的茄子2 小时前
深入理解JavaScript闭包:从入门到精通的实战指南
前端·javascript·面试
金色光环2 小时前
【Modbus学习笔记】stm32实现Modbus
笔记·stm32·学习