MySQL 数据访问与查询优化:提升性能的实战策略和解耦优化技巧(三)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MySQL 数据访问与查询优化:提升性能的实战策略和解耦优化技巧(三)

优化 LIMIT 分页

在大多数应用场景中,都需要将数据进行分页,一般会使用 LIMIT + offset 方法实现,同时+上合适的 order by 语句;若这种方式有索引的帮助,效率通常会不错,否则的话就需要大量的文件排序操作,还有一种情况,当偏移量非常大时,前面的大部分数据都会被抛弃,这样的代码太高;要优化这种查询时,要么在页面中限制分页的数量,要么优化大偏移量的性能,优化此类查询的最简单办法就是尽可能的使用覆盖索引,而不是查询所有的列

mysql> explain select film_id,description from film order by title limit 50,5;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | film  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select film_id,description from film inner join (select film_id from film order by title limit 50,5) as lim using(film_id);
+----+-------------+------------+------------+--------+---------------+-----------+---------+-------------+------+----------+-------------+
| id | select_type | table      | partitions | type   | possible_keys | key       | key_len | ref         | rows | filtered | Extra       |
+----+-------------+------------+------------+--------+---------------+-----------+---------+-------------+------+----------+-------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL    | NULL          | NULL      | NULL    | NULL        |   55 |   100.00 | NULL        |
|  1 | PRIMARY     | film       | NULL       | eq_ref | PRIMARY       | PRIMARY   | 2       | lim.film_id |    1 |   100.00 | NULL        |
|  2 | DERIVED     | film       | NULL       | index  | NULL          | idx_title | 514     | NULL        |   55 |   100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+-----------+---------+-------------+------+----------+-------------+

通过如上对比,能够发现第二条 SQL 语句扫描的数据行更少,所以可以通过这种方式对 LIMIT 进行优化

优化 union

MySQL 通过创建并填充临时表的方式来执行 union 查询,因此很多优化策略在 union 查询中都没法很好的使用到;经常需要手动的将 where、limit、order by 等子句下推到各个子查询中,以便于优化器可以充分利用这些条件进行优化

除非服务器确实需要消除重复行数据,否则一定要使用 union all,因为没有 all 关键字,MySQL 会在查询时给临时表 + 上 distinct 关键字,这个操作的代价很高

用户自定义变量

用户自定义变量是一个容易被遗忘的 MySQL 特性,但若能用好的话,在某些场景下可以写出非常高效的查询语句,在查询中混合使用过程化、关系化逻辑时,自定义变量会非常有用

用户自定义变量是一个可以用来存储内容的临时容器,在连接 MySQL 整个过程中都存在

使用自定义变量

mysql> set @min_actor:=(select min(actor_id) from actor);
Query OK, 0 rows affected (0.00 sec)
mysql> select @min_actor;
+------------+
| @min_actor |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)
mysql> set @last_week:=current_date-interval 1 week;
Query OK, 0 rows affected (0.00 sec)
mysql> select @last_week;
+------------+
| @last_week |
+------------+
| 2023-05-23 |
+------------+

自定义变量的限制

1、无法使用查询缓存

2、不能在使用常量或标识符的地方使用自定义变量,例如:表名、列名或 LIMIT 子句

3、用户自定义变量的生命周期在一个连接内有效,所以不能用它们来作连接之间的通信

4、不能显示声明自定义变量的类型

5、MySQL 在特定场景下,可能会将这些变量优化掉,可能导致代码不按预想的方式运行

6、赋值符号 := 优先级非常低,所以在使用赋值表达式时应该明确使用的符号

7、使用未定义变量,不会产生任何语法错误

自定义变量的使用案例

  1. 变量赋值后,使用此变量
mysql> select @rownum:=@rownum+1 as rownum,actor_id from actor limit 10;
+--------+----------+
| rownum | actor_id |
+--------+----------+
|      1 |       58 |
|      2 |       92 |
|      3 |      182 |
|      4 |      118 |
|      5 |      145 |
|      6 |      194 |
|      7 |       76 |
|      8 |      112 |
|      9 |       67 |
|     10 |      190 |
+--------+----------+
  1. 避免重复查询刚刚更新的数据 > 当需要高效的更新一条记录的时间戳时,同时希望查询当前记录中存放的时间戳是什么,如下:
    一般情况下,我们都是先把数据更新上去后,再通过主键查询这条数据的信息
create table t1(id int,lastUpdated date);
insert into t1 values(1,now());
update t1 set lastUpdated=now() where id=1;
select lastUpdated from t1 where id=1;

上述的操作可以分为两个步骤来操作,直接使用自定义变量代替 >

update t1 set lastUpdated=now() where id=1 and @now:=now();
select @now;
  1. 确认取值的顺序性 > 在赋值、读取变量时可能是在查询的不同阶段
mysql> set @rownum:=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @rownum:=@rownum+1 as cnt,actor_id from actor where @rownum<=1;
+------+----------+
| cnt  | actor_id |
+------+----------+
|    1 |       58 |
|    2 |       92 |
+------+----------+

where、select 在查询的不同阶段执行,所以可以看到两条记录被查询出来,这不符合预期

set @rownum:=0;
select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1 order by first_name;

当引入 order by 子句以后,发现打印出了全部结果,这是因为 order by 引入了文件排序,而 where 条件是在文件排序之前取值的.

解决这个问题的关键在于:让变量的赋值、取值发生在执行查询的同一阶段

mysql> set @rownum:=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @rownum as cnt,actor_id from actor where (@rownum:=@rownum+1)<=1;
+------+----------+
| cnt  | actor_id |
+------+----------+
|    1 |       58 |
+------+----------+

个人在工作当中,常常会用自定义变量在测试环境写存储过程,主要是为了简便演示>测试数据的工作,提高工作中的效率

海量数据解耦优化处理

当操作大数据量时,解耦是必不可少的,可靠的反范式化设计、业务层面的解耦上升到数据库设计层面

单行数据字段不宜太多,尽量能让一些字段分散到中间表中,根据其使用场景来取字段信息,

MySQL 5.7 版本支持 JSON 类型字段,此字段内容建议不要存无用的信息,因为内容越多占用的磁盘空间越高,会导致我们单表所能存储的数据量大小大大降低

在表数据量逐渐递增时,原有的业务需要关联多张表获取数据,到后面这将会是一个很大头的问题,所以,在前期的数据库设计、适当的反范式化设计是相当重要的,尽管需要多开发一些业务代码以及处理数据的一致性逻辑等,但为我们的瓶颈>MySQL 带来极大的益处,减少了 IO 成本开销,减少给数据库带来的压力!

冗余那些 JOIN 表只需要 1~2 个字段信息的内容,可以将它冗余到表中,当后续这两个字段内容发生改变时,可以通过事件的方法触达> 异步更新表中的内容,以达到数据的最终一致性

在业务设计开发过程中,减少 IO 次数,频繁请求数据库的操作,尽量以一条可视化 SQL(不通过 MyBatis-Plus 便捷操作> 它为我们提供了便利,并不是让我们这么消耗数据库性能的,它更方便的是为了给我们提供单表内的查询,关于多表或基于多字段统计时尽量还是使用可视化 SQL 语句去编码实现)

在处理大表数据时,不应该一次性把所有的数据全捞出来,高并发处理的场景都是基于分而治之的思想去做的,以批次的方式去处理业务数据,比如:1000、2000、3000 方式,控制好数据操作的时长,以这个时长为间隔去处理下一批数据> 分布式任务调度以串行的方式去处理每一条任务,当然,处理的前提是表的索引、执行效率要保障好!

基于测试、生产环境,后续服务资源升级或降级,批次处理的这个数量应该以配置的形式去处理,可以基于我们的实际诉求去动态调整

接口设计也是一方面的提升,接口的设计也应该遵循于设计模式的初衷,如:单一职责;每个接口处理的数据应该独立区分,不应该所有的事情都由一个接口去完成,接口返回的参数内容应该也是一一对应的,过多返回无用的字段对于我们网络传输是效率极低下的,即使字段是空值,但不要忽略我们 Java 中每种类型只要你定义了,它就一定会占用空间的,更何况说,基于前后端交互,它的序列化机制、网络交互机制所需要耗费的成本资源了!

比如:查询会员等级配置,前端只需要用到等级Id、等级编码、等级名称,那么就可以基于解耦,单独定义一个实体只存放这三个字段,在操作 DB 时,不要 SELECT *,也只查询 id、level_code、level_name,这样既减少了数据库的 IO 交互,又减少了网络传输中的成本,也减少了序列化实体的字段数

其次,缓存要运用到位,不是所有的数据都是需要经过数据库去取出来的,对于一些基础数据,一旦它确认了,就不会再发生改变时,完全可以将它以时效性的机制存入到 Redis 缓存中,让它基于内存去交互,而不是经过数据库->磁盘交互,内存->磁盘时长约等于 1:1000,用好缓存的同时,要保证缓存一致性,这就又引出了缓存、MySQL 数据一致性问题,但 MySQL 变更后同时要更新缓存信息或删除缓存信息

服务中处理缓存的地方尽量将它统一放在一个地方处理,若出现了缓存、MySQL 数据一致性问题时,你可能都不知道是哪个地方使用了缓存,这是编码规范的一部分;为了确保缓存一致性,采用删除缓存的方式并不是那么可靠,因为 Redis 中有缓存过期策略这个机制,即使你调用了删除,但有可能它不会立马就删除这些缓存,它会基于过期策略有一个较小的缓冲期

异步对于提高整体效率也是一个很重要的部分,对于那些没有依赖性的数据时,完全可以让它以异步的方式先去处理后返回数据,CompletableFuture 异步编排是一个很好的并发编程 API

基于高并发场景下,多个用户同时访问同一批数据,会对 MySQL 产生大量无效的、重复的请求,此时,可以基于此分析 > 重复的请求一定是一样的数据,那么此数据我们可以在这个时刻先用缓存的方式存储起来,将给予 MySQL 压力转移到缓存中间件,访问方式以磁盘转换为内存,提高接口吞吐量、减少响应的时长

基于此场景,对于这个接口可以对访问数据库的地方 + 分布式读锁,Redis setex 是一个较好的选择

解耦部分:

1、首先抢到锁的人先请求数据库获取这部分数据,然后将其缓存起来;后面的用户线程可以直接读取缓存起来的这部分数据,以达到再同一个时刻访问同一份数据时,可以减少对 MySQL 造成的压力,其而言之,这就是 缓存击穿

2、保证访问这部分数据内容的运行时间能让它达到最优解,以我前面介绍的方式对数据库表以及数据部分进行优化,确保在最短时间内数据能够到达!

详解:当多个用户同时进来时,优先从缓存中读取数据,若缓存中无数据,第一个用户线程会抢到这把锁>查询数据库返回数据、存入缓存,那么其他用户线程就拿不到这把锁了,此时我们可以让它先阻塞一小段时间 > Thread.sleep(500);,等这个时间过去了,其他的用户线程可以再从缓存中读取数据,后续此类高频率请求的接口可以大大提升效率

总结

该篇博文简要分析了为什么会查询慢?以不同角度的优化方式提高数据访问的效率,简单地分析 MySQL 语法解析器、查询优化器处理的过程,以理论+实战结合方式加深印象,提高辨识度;最主要的大数据量查询优化、海量数据优化处理,这些场景都是博主在实际工作中处理过的,实战是校验真理的唯一标准,解耦设计、异步、缓存、适当反范式化设计等,都是一些日常中会使用的技术场景,后面会有文章主要讲述 MySQL 锁、MVCC、分区等概念|实战

大家有什么问题或者更好的建议,可以在文末评论,一起探讨喔,主打技术共同进步、提升!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!



相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3天前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
7天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
113 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
7天前
|
SQL 关系型数据库 MySQL
MySQL慢查询优化、索引优化、以及表等优化详解
本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
MySQL慢查询优化、索引优化、以及表等优化详解
|
7天前
|
缓存 NoSQL 关系型数据库
Redis和Mysql如何保证数据⼀致?
在项目中,为了解决Redis与Mysql的数据一致性问题,我们采用了多种策略:对于低一致性要求的数据,不做特别处理;时效性数据通过设置缓存过期时间来减少不一致风险;高一致性但时效性要求不高的数据,利用MQ异步同步确保最终一致性;而对一致性和时效性都有高要求的数据,则采用分布式事务(如Seata TCC模式)来保障。
39 14
|
10天前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
39 9
|
12天前
|
搜索推荐 关系型数据库 MySQL
mysql like查询优化
通过合理的索引设计、使用全文索引、优化查询结构以及考虑分片和分区表,可以显著提高MySQL中 `LIKE`查询的性能。针对不同的应用场景选择合适的优化策略,能够有效地提升数据库查询效率,减少查询时间。希望这些方法和技巧能帮助您优化MySQL数据库中的模糊查询。
55 4
|
18天前
|
SQL 关系型数据库 MySQL
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
38 1
|
8天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
22 4
|
6天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
19 1
|
15天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
77 1