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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 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,订阅一波不再迷路

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



相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1天前
|
安全 关系型数据库 MySQL
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
10 3
|
1天前
|
SQL 关系型数据库 MySQL
不允许你不知道的 MySQL 优化实战(一)
不允许你不知道的 MySQL 优化实战(一)
|
2天前
|
SQL 关系型数据库 MySQL
Spring_jdbc数据连接池(mysql实现增、删、改、查)
Spring_jdbc数据连接池(mysql实现增、删、改、查)
8 0
|
2天前
|
存储 缓存 关系型数据库
掌握MySQL数据库这些优化技巧,事半功倍!
掌握MySQL数据库这些优化技巧,事半功倍!
|
3天前
|
缓存 关系型数据库 MySQL
MySQL数据库优化技巧:提升性能的关键策略
索引是提高查询效率的关键。根据查询频率和条件,创建合适的索引能够加快查询速度。但要注意,过多的索引可能会增加写操作的开销,因此需要权衡。
|
3天前
|
SQL Oracle 关系型数据库
下次老板问你MySQL如何优化时,你可以这样说,老板默默给你加工资
现在进入国企或者事业单位做技术的网友越来越多了,随着去O的力度越来越大,很多国企单位都开始从Oracle向MySQL转移,相对于Oracle而言,MySQL最大的问题就是性能,所以,这个时候,在公司如果能够处理好MySQL的性能瓶颈,那么你也就很容易从人群中脱颖而出,受到老板的青睐。
22 1
|
4天前
|
存储 数据可视化 关系型数据库
MySQL字段的时间类型该如何选择?千万数据下性能提升10%~30%🚀
本文探讨MySQL中时间类型的选择,阐述datetime、timestamp、整形时间戳等类型特点以及它们在千万级数据量下的查询性能
MySQL字段的时间类型该如何选择?千万数据下性能提升10%~30%🚀
|
4天前
|
关系型数据库 MySQL 中间件
【MySQL实战笔记】07 | 行锁功过:怎么减少行锁对性能的影响?-02 死锁和死锁检测
【4月更文挑战第19天】在高并发环境下,死锁发生在多个线程间循环等待资源时,导致无限期等待。MySQL中,死锁可通过`innodb_lock_wait_timeout`参数设置超时或`innodb_deadlock_detect`开启死锁检测来解决。默认的50s超时可能不适用于在线服务,而频繁检测会消耗大量CPU。应对热点行更新引发的性能问题,可以暂时关闭死锁检测(风险是产生大量超时),控制并发度,或通过分散记录减少锁冲突,例如将数据分拆到多行以降低死锁概率。
19 1
|
3天前
|
SQL 存储 关系型数据库
MySQL Cluster集群安装及使用
MySQL Cluster集群安装及使用
|
8天前
|
关系型数据库 MySQL 数据库
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
33 4