难题来了:分库分表后,查询太慢了,如何优化?

说在前面:

尼恩社群中,很多小伙伴反馈, Sharding-JDBC 分页查询的速度超级慢, 怎么处理?

反馈这个问题的小伙伴,很多很多。

而且这个问题,也是面试的核心难题。前段时间,有小伙伴反馈,在面试中遇到了这个问题

分库分表后,分页查询太慢了,如何优化?

尼恩提示:分库分表的知识,既是面试的核心知识,又是开发的核心知识。

所以,这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V120版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

需要《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取

文章目录

    • 说在前面:
    • 查询太慢的优化三种方法:
    • 首先,分析慢的根本原因,原来是 Sharding-JDBC 做了分页修正
      • 功能和性能的冲突:从0开始的性能瓶颈
      • Sharding-JDBC的优化
    • 流式查询的弊端
    • 优化方案1:组合使用 ES 搜索做分页,
    • 优化方案2: 禁止跳页查询法
    • 优化方法3:二次查询法
    • 说在最后
    • 推荐阅读

查询太慢的优化三种方法:

分库分表后,查询太慢了,如何优化?大致三种方法:

  • 方式一:组合使用 ES 搜索做分页,
  • 方式二:禁止跳页
  • 方式三:二次查询法

第一种方式,需要引入 es,这里不做展开,有兴趣的可以来尼恩的《技术自由圈》社群交流。

这里就梳理一下,第二种禁止跳页、第三种二次查询法。

首先,分析慢的根本原因,原来是 Sharding-JDBC 做了分页修正

Sharding-JDBC从多个数据库获取分页数据,与单数据库的场景是不同的。

假设每10条数据为一页,取第2页数据。

在分片环境下获取LIMIT 10, 10,归并之后再根据排序条件取出前10条数据是不正确的。

举例说明,若SQL为:

SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;

下图展示了不进行SQL的改写的分页执行结果:

通过图中所示,想要取得两个表中共同的按照分数排序的第2条和第3条数据,理论上,应该是95和90。

实际上呢?

由于执行的SQL只能从每个表中获取第2条和第3条数据,即从t_score_0表中获取的是90和80;

从t_score_0表中获取的是85和75。

因此进行结果归并时,只能从获取的90,80,85和75之中进行归并,那么,无论怎么实现,结果归并之后,都不可能获得正确的结果。

正确的做法是将分页条件改写为LIMIT 0, 3,取出所有前两页数据,再结合排序条件计算出正确的数据。 下图展示了进行SQL改写之后的分页执行结果。

功能和性能的冲突:从0开始的性能瓶颈

注意,这里有个大问题:

为了结果不出错,归并之前的查询,是0开始, 结果才可能是对的。

然而,查询偏移量过大的分页会导致数据库获取数据性能低下,

以MySQL为例:

如果不是分库分表,这句SQL会使得MySQL在无法利用索引的情况下跳过1000000条记录后,再获取10条记录,其性能可想而知。

然而, 在分库分表的情况下(假设分为2个库),为了保证数据的正确性,SQL会改写为:

即将偏移量前的记录全部取出,并仅获取排序后的最后10条记录。

这会在数据库本身就执行很慢的情况下,进一步加剧性能瓶颈。

因为原SQL仅需要传输10条记录至客户端,而改写之后的SQL则会传输1,000,010 * 2的记录至客户端。

Sharding-JDBC的优化

(1)采用流式处理 + 归并排序的方式来避免内存的过量占用。

由于SQL改写不可避免的占用了额外的带宽,但并不会导致内存暴涨。

与直觉不同,大多数人认为Sharding-JDBC会将1,000,010 * 2记录全部加载至内存,进而占用大量内存而导致内存溢出。

但由于每个结果集的记录是有序的,因此Sharding-JDBC每次仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当前游标指向而已。 对于本身即有序的待排序对象,归并排序的时间复杂度仅为O(n),性能损耗很小。

(2)Sharding-JDBC对仅落至多分片的查询进行进一步优化。

落至单分片查询的请求并不需要改写SQL也可以保证记录的正确性,因此在此种情况下,Sharding-JDBC并未进行SQL改写,从而达到节省带宽的目的。

一般情况下,性能慢,都是第一种情况。流式查询看上去很好,但也有大大的弊端。

流式查询的弊端

采用游标查询的方式的缺点很明显。

流式(游标)查询需要注意:当前查询会独占连接!必须先读取(或关闭)结果集中的所有行,然后才能对连接发出任何其他查询,否则将引发异常!

执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后需要自己关闭。

由于MySQL_Server不知道客户端什么时候将数据消费完,而自身的对应表可能会有DML写入操作,此时MySQL_Server需要建立一个临时空间来存放需要拿走的数据

因此对于当你启用useCursorFetch读取大表的时候,会看到MySQL上的几个现象:

  1. IOPS 飙升,因为需要返回的数据需要写入到临时空间中,存在大量的 IO 读取和写入,此流程可能会引起其它业务的写入抖动
  2. 磁盘空间飙升,写入临时空间的数据会在读取完成或客户端发起 ResultSet#close 操作时由 MySQL_Server 回收
  3. 客户端 JDBC 发起sql_query,可能会有长时间等待,这段时间为MySQL_Server准备数据阶段。但是 普通查询等待时间与游标查询等待时间原理上是不一致的: 前者是在读取网络缓冲区的数据,没有响应到业务层面;后者是 MySQL 在准备临时数据空间,没有响应到 JDBC
  4. 数据准备完成后,进行到传输数据阶段,网络响应开始飙升,IOPS 由"写"转变为"读"

这就是,尼恩社群很多小伙伴反馈, 分表后 分页查询慢的原因。基本上是分页越大后续的查询越耗资源

如何解决呢?

优化方案1:组合使用 ES 搜索做分页,

第一种方式,需要引入 es,这里不做展开,有兴趣的可以来尼恩的《技术自由圈》社群交流。

优化方案2: 禁止跳页查询法

由于LIMIT并不能通过索引查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案

或通过记录上次查询结果的最后一条记录的ID进行下一页的查询:

如果不是id列, 假设排序的列为col,禁止跳页查询法的两个步骤大致如下:

(1)用正常的方法取得第一页数据,并得到第一页记录的 max_col 最大值;

(2)每次翻页,将

order by col offset X limit Y;

改写成

order by col where col>$time_max limit Y;

以保证每次只返回一页数据,性能为常量。

优化方法3:二次查询法

假设排序的列为col,二次查询法的两个步骤大致如下:

(1)SQL改写,将

order by col offset X limit Y;

改写成

order by col offset X/N limit Y;

(2)多页返回,找到最小值col_min;

(3)between二次查询

order by col between col_min and col_i_max;

(4)设置虚拟col_min,找到col_min在各个分库的offset,从而得到col_min在全局的offset;

(5)得到了col_min在全局的offset,自然得到了全局的offset X limit Y;

例子:分表结构

CREATE TABLE `student_time_0` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULL,`name` varchar(200) COLLATE utf8_bin DEFAULT NULL,`age` tinyint(3) unsigned DEFAULT NULL,`create_time` bigint(20) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=674 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

有这样的三个表,student_time_0, student_time_1, student_time_2, 以 user_id 作为分表键,根据表数量取模作为分表依据
这里先构造点数据,

insert into student_time (`name`, `user_id`, `age`, `create_time`) values (?, ?, ?, ?)

主要是为了保证 create_time 唯一,比较好说明问题,

int i = 0;
try (Connection conn = dataSource.getConnection();PreparedStatement ps = conn.prepareStatement(insertSql)) {do {ps.setString(1, localName + new Random().nextInt(100));ps.setLong(2, 10086L + (new Random().nextInt(100)));ps.setInt(3, 18);ps.setLong(4, new Date().getTime());int result = ps.executeUpdate();LOGGER.info("current execute result: {}", result);Thread.sleep(new Random().nextInt(100));i++;} while (i <= 2000);

三个表的数据分别是 673,678,650,各个表数据不一样,

接下来,做一个这样的分页查询

select * from student_time ORDER BY create_time ASC limit 1000, 5;

student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表, sharding-jdbc 会改写为

select * from student_time ORDER BY create_time ASC limit 0, 1005;

即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢

  • 第一个办法禁止跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询
  • 第二个办法是二次查询法

这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均,333,根据这个 limit 333,5 在三个表里进行查询

t0
334 10158 nick95  18  1641548941767
335 10098 nick11  18  1641548941879
336 10167 nick51  18  1641548942089
337 10167 nick3 18  1641548942119
338 10170 nick57  18  1641548942169t1
334 10105 nick98  18  1641548939071   最小
335 10174 nick94  18  1641548939377
336 10129 nick85  18  1641548939442
337 10141 nick84  18  1641548939480
338 10096 nick74  18  1641548939668t2
334 10184 nick11  18  1641548945075
335 10109 nick93  18  1641548945382
336 10181 nick41  18  1641548945583
337 10130 nick80  18  1641548945993
338 10184 nick19  18  1641548946294  最大

第一遍的目标是啥,查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,

其实主要是最小值,因为拿着最小值去查,以后我就能知道这个最小值在每个表里处在什么位置,

order by col between  col_min and col_i_max;

那这里我也想着拿到这个条件,所以我将第一遍查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,

这里,其实主要是最小值,因为我拿着最小值去查以后我就能知道这个最小值在每个表里处在什么位置,

t0
322 10161 nick81  18  1641548939284
323 10113 nick16  18  1641548939393
324 10110 nick56  18  1641548939577
325 10116 nick69  18  1641548939588
326 10173 nick51  18  1641548939646t1
334 10105 nick98  18  1641548939071
335 10174 nick94  18  1641548939377
336 10129 nick85  18  1641548939442
337 10141 nick84  18  1641548939480
338 10096 nick74  18  1641548939668t2
297 10136 nick28  18  1641548939161
298 10142 nick68  18  1641548939177
299 10124 nick41  18  1641548939237
300 10148 nick87  18  1641548939510
301 10169 nick23  18  1641548939715

我只贴了前五条数据,为了方便知道偏移量,每个分表都使用了自增主键,

我们可以看到前一次查询的最小值分别在其他两个表里的位置分别是 322-1 和 297-1,

那么,对于总体来说,这个时间的起始位置,应该是在 322 - 1 + 334-1 + 297 - 1 = 951

那么,只要对后面的数据最多每个表查 1000 - 951 + 5 = 54 条数据,再进行合并排序就可以获得最终正确的结果。

这个就是的二次查询法。

可见,二次查询法很麻烦, 不如禁止跳页法,或者 es组合方法,直接,有效。

说在最后

分库分表面试题,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》。

此宝典,尼恩一直持续更新,持续加入:最新的真题、最新难题,最新版本可以找40岁老架构尼恩领取。

并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

最终,让面试官爱到 “不能自已、口水直流”

offer, 也就来了。

推荐阅读

《百亿级访问量,如何做缓存架构设计》

《多级缓存 架构设计》

《消息推送 架构设计》

《阿里2面:你们部署多少节点?1000W并发,当如何部署?》

《美团2面:5个9高可用99.999%,如何实现?》

《网易一面:单节点2000Wtps,Kafka怎么做的?》

《字节一面:事务补偿和事务重试,关系是什么?》

《网易一面:25Wqps高吞吐写Mysql,100W数据4秒写完,如何实现?》

《亿级短视频,如何架构?》

《炸裂,靠“吹牛”过京东一面,月薪40K》

《太猛了,靠“吹牛”过顺丰一面,月薪30K》

《炸裂了…京东一面索命40问,过了就50W+》

《问麻了…阿里一面索命27问,过了就60W+》

《百度狂问3小时,大厂offer到手,小伙真狠!》

《饿了么太狠:面个高级Java,抖这多硬活、狠活》

《字节狂问一小时,小伙offer到手,太狠了!》

《收个滴滴Offer:从小伙三面经历,看看需要学点啥?》

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

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

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

相关文章

windows 用vs创建cmake工程并编译opencv应用项目生成exe流程简述

目录 前言一、安装opencv&#xff08;1&#xff09;下载&#xff08;2&#xff09;双击安装&#xff08;3&#xff09;环境变量和system文件夹设置 二、打开vs创建项目三、编辑cpp&#xff0c;.h&#xff0c;cmakelist.txt文件&#xff08;1&#xff09;h文件&#xff08;2&…

【Python从入门到进阶】41、有关requests代理的使用

接上篇《40、requests的基本使用》 上一篇我们介绍了requests库的基本使用&#xff0c;本篇我们来学习requests的代理。 一、引言 在网络爬虫和数据抓取的过程中&#xff0c;我们经常需要发送HTTP请求来获取网页内容或与远程服务器进行通信。然而&#xff0c;在某些情况下&…

通过在Z平面放置零极点的来设计数字滤波器

文章来源地址&#xff1a;https://www.yii666.com/blog/393376.html 通过在Z平面放置零极点的来设计数字滤波器 要求&#xff1a;设计一款高通滤波器&#xff0c;用在音频信号处理过程中&#xff0c;滤掉100Hz以下的信号。 实现方法&#xff1a;通过在Z平面放置零极点的来设…

数据结构与算法【02】—线性表

CSDN系列专栏&#xff1a;数据结构与算法专栏 针对以前写的数据结构与算法系列重写(针对文字描述、图片、错误修复)&#xff0c;改动会比较大&#xff0c;一直到更新完为止 前言 通过前面数据结构与算法基础知识我们知道了数据结构的一些概念和重要性&#xff0c;那么本章总结…

【UE 材质】简单的闪闪发光材质

效果 节点 参考视频&#xff1a; https://www.bilibili.com/video/BV1uK411y737/?vd_source36a3e35639c44bb339f59760641390a8

MySQL(8):聚合函数

聚合函数介绍 聚合函数&#xff1a; 对一组数据进行汇总的函数&#xff0c;输入的是一组数据的集合&#xff0c;输出的是单个值。 聚合函数类型&#xff1a;AVG(),SUM(),MAX(),MIN(),COUNT() AVG / SUM 只适用于数值类型的字段&#xff08;或变量&#xff09; SELECT AVG(…

【LeetCode】每日一题 2023_11_4 数组中两个数的最大异或值

文章目录 刷题前唠嗑题目&#xff1a;数组中两个数的最大异或值题目描述代码与解题思路 结语 刷题前唠嗑 LeetCode? 启动&#xff01;&#xff01;&#xff01; 题目&#xff1a;数组中两个数的最大异或值 题目链接&#xff1a;421. 数组中两个数的最大异或值 题目描述 代…

前端埋点方式

前言&#xff1a; 想要了解用户在系统中所做的操作&#xff0c;从而得出用户在本系统中最常用的模块、在系统中停留的时间。对于了解用户的行为、分析用户的需求有很大的帮助&#xff0c;想实现这种需求可以通过前端埋点的方式。 埋点方式&#xff1a; 1.什么是埋点&#xff1f…

基于Jenkins实现接口自动化持续集成,学完涨薪5k

一、JOB项目配置 1、添加描述 可选选项可填可不填 2、限制项目的运行节点 节点中要有运行环境所需的配置 节点配置教程&#xff1a;https://blog.csdn.net/YZL40514131/article/details/131504280 3、源码管理 需要将脚本推送到远程仓库中 4、构建触发器 可以选择定时构建…

【python】路径管理+路径拼接问题

路径管理 问题相对路径问题绝对路径问题 解决os库pathlib库最终解决 问题 环境&#xff1a;python3.7.16 win10 相对路径问题 因为python的执行特殊性&#xff0c;使用相对路径时&#xff0c;在不同路径下用python指令会有不同的索引效果&#xff08;python的项目根目录根据执…

Temp directory ‘C:\WINDOWS\TEMP‘ does not exist

问题描述 解决方法 管理员权限问题&#xff0c;进入temp文件夹更改访问权限即可。 点击 temp文件夹 属性 -> 安全 -> 高级 -> 更改主体Users权限 给读取和写入权限 参考博客 开发springboot项目时无法启动Temp directory ‘C: \WINDOWS\TEMP‘ does not exist

Python库学习(十二):数据分析Pandas[下篇]

接着上篇《Python库学习(十一):数据分析Pandas[上篇]》,继续学习Pandas 1.数据过滤 在数据处理中&#xff0c;我们经常会对数据进行过滤&#xff0c;为此Pandas中提供mask()和where()两个函数&#xff1b; mask(): 在 满足条件的情况下替换数据&#xff0c;而不满足条件的部分…

leetcode-经典面/笔试题目

1.消失的数字 面试题 17.04. 消失的数字 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/missing-number-lcci/ 这个题目当然有好几种解法&#xff0c;这里我推荐一种比较优秀的思路&#xff0c;也就是单身狗思路&#xff1a;异或。 异或的特点是相异…

账户权限控制

1.首先配置一个单群组4节点的链 1.1创建操作目录 cd ~ && mkdir -p fisco && cd fisco 1.2下载国内脚本 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/releases/v2.9.1/build_chain.sh && chmod ux bu…

【KVM】软件虚拟化和硬件虚拟化类型

前言 大家好&#xff0c;我是秋意零。 今天介绍的内容是虚拟化技术以及软件虚拟化和硬件虚拟化。 &#x1f47f; 简介 &#x1f3e0; 个人主页&#xff1a; 秋意零&#x1f525; 账号&#xff1a;全平台同名&#xff0c; 秋意零 账号创作者、 云社区 创建者&#x1f9d1; 个…

系统提示缺少或找不到d3dcompiler_43.dll文件的详细修复教程

今天我来给大家分享一下关于d3dcompiler_43.dll缺失的4个修复方法。 首先&#xff0c;我们来了解一下d3dcompiler_43.dll的作用。它是DirectX中的一个组件&#xff0c;用于编译Shader和Pixel着色器代码。如果缺少了这个文件&#xff0c;就会导致游戏或应用程序无法正常运行。 …

全能数据分析软件 Tableau Desktop 2019 mac中文版功能亮点

Tableau Desktop 2019 mac是一款专业的全能数据分析工具&#xff0c;可以让用户将海量数据导入并记性汇总&#xff0c;并且支持多种数据类型&#xff0c;比如像是编程常用的键值对、哈希MAP、JSON类型数据等&#xff0c;因此用户可以将很多常用数据库文件直接导入Tableau Deskt…

适合新手自学的网络安全基础技能“蓝宝书”:《CTF那些事儿》

文章目录 内容简介读者对象专家推荐目录赠书活动 CTF比赛是快速提升网络安全实战技能的重要途径&#xff0c;已成为各个行业选拔网络安全人才的通用方法。但是&#xff0c;本书作者在从事CTF培训的过程中&#xff0c;发现存在几个突出的问题&#xff1a; 线下CTF比赛培训中存在…

力扣:149. 直线上最多的点数(Python3)

题目&#xff1a; 给你一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱…

stm32整理(三)ADC

1 ADC简介 1.1 ADC 简介 12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道&#xff0c;可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续 采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位…