优化-查询数据接口太慢

有一个查询接口,主业务表有几万多条数据,没超过十万,由于没有使用分页,所以每次查询都要返回大几万的数据,然后问题是前端页面查询数据显示数据要转很久。

压缩响应体大小

我发现查询的时间是1秒多,但是浏览器调式看到接口花的时间是3秒多。

发现是响应体太大了,响应体有21.97MB,下载花了两秒多。

查询资料得知,http请求需要下载响应体,如果响应体太大会导致Content Download时间过长,下载HTTP响应的时间(包含头部和响应体)。

  优化措施:   

1、通过条件Get请求,对比If-Modified-Since和Last-Modified时间,确定是否使用缓存中的组件,服务器会返回“304 Not Modified”状态码,减小响应的大小;   

2、移除重复脚本,精简和压缩代码,如借助自动化构建工具grunt、gulp等;   

3、压缩响应内容,服务器端启用gzip压缩,可以减少下载时间;

 响应体太大,服务器开启响应压缩:

server:
  compression:
  	# 开启压缩
    enabled: true
    # 压缩的响应内容
    mime-types:
      - application/json
      - application/xml
      - application/javascript
      - text/html
      - text/xml
      - text/plain
      - text/css
      - text/javascript
    # 响应体大小达到2048kb才压缩
    min-response-size: 2048

开启后确实下载内容花的时间变短了很多:

响应下载的时间的打下来了,但是等待服务器响应的时间还是有点长,就是接口的问题了。先去看程序有没有问题,执行了一下发现窗口一直再打印查询条目,因为查询的条数很多,他会将每条查询出来的条目row打印出来,于是把mybatis的日志配置先注释了,结果真的快了很多。但还是不够快。程序上检查了一圈,感觉没的优化了,就去看查询的sql语句。

#MyBatis相关配置
mybatis:
  mapperLocations: classpath*:/mapper/*.xml
  typeAliasesPackage: com.huishi.entity
  configuration:
    map-underscore-to-camel-case: false
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

改用:

logging:
  level:
    com.lin.mapper: debug

SQL语句

这是这个接口的主要查询sql:

SELECT
        EC1.CLASS_CODE CLASSCODE1,
        EC2.CLASS_CODE CLASSCODE2,
        EC3.CLASS_CODE CLASSCODE3,
        EI.ID, EI. EQUIPMENT_CODE, EI. ISSUE_SERIES_CODE, EI. OLD_SELF_CODE, EI. EQUIP_NAME, EI. EQUIP_TYPE, EI. USE_DEPART, EI. SCOUT_SYSTEM, EI.
        CLASS_CODE, EI. "USAGE", EI. MISSION, EI. EQUIP_POS, EI. MANAGER, EI. FIX_DEPART, EI. EQUIP_NUM, EI. EQUIP_UNIT_PRICE, EI.
        EQUIP_SUM_PRICE, EI. EQUIP_STATUS, EI. USE_STATUS, EI. QUALITY_LEVEL, EI. GIVEN_DATE, EI. START_USE_DATE, EI. PRODUCE_DATE, EI. PRODUCE_FACTORY, EI.
        BATCH_CODE, EI. USE_DEPART_CODE, EI. SEQUENCE_CODE, EI. COMM_INFO, EI. EQUIP_CONF, EI. COUNT_FLAG, EI. MARK_FLAG, EI. EQUIP_SOURCE, EI. IP_ADDRESS, EI. UPDATE_TIME, EI. CABI_CODE, EI.
        IS_GENERAL, EI. IMAGE_PATH1, EI. IMAGE_PATH2, EI. FUNDS_SOURCE, EI. FORCE_SYSTEM, EI. SERVICE_LIFE, EI. UNIT_CODE, EI. DOC_ID
        FROM EQUIPMENT_INFO EI
        LEFT JOIN DICT_EQUIPMENT_CLASS1 EC1 ON EC1.CLASS_NAME = trim(SUBSTR(EI.CLASS_CODE, 1, INSTR(EI.CLASS_CODE, '/', 1, 1) - 1))
        LEFT JOIN DICT_EQUIPMENT_CLASS2 EC2 ON EC2.CLASS_NAME = trim(SUBSTR(EI.CLASS_CODE, INSTR(EI.CLASS_CODE, '/', 1, 1) + 1, INSTR(EI.CLASS_CODE, '/', 1, 2) - INSTR(EI.CLASS_CODE, '/', 1, 1) - 1))
        LEFT JOIN DICT_EQUIPMENT_CLASS3 EC3 ON EC3.CLASS_NAME = trim(SUBSTR(EI.CLASS_CODE, INSTR(EI.CLASS_CODE, '/', 1, 2) + 1))
        where EI.DEL_FLAG = '0' and EI.USE_STATUS != '已报废'

索引方案

CREATE INDEX 索引名 ON 表名(列名);

除了单列索引,还可以创建包含多个列的复合索引。
CREATE INDEX 索引名 ON 表名(列名1, 列名2, 列名3, ...);

删除索引也非常简单。
DROP INDEX 索引名;

查看某个表中的所有索引也同样简单。
SELECT * FROM ALL_INDEXES WHERE TABLE_NAME = '表名'

还可以查看某个表中建立了索引的所有列。
SELECT * FROM ALL_IND_COLUMNS WHERE TABLE_NAME = '表名'

常见的就是给自动添加索引了。

给经常在where后面的字段加了索引,查看sql的执行计划:

EXPLAIN PLAN FOR my_querySql;
            
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

  1. Plan hash value: 查询执行计划的哈希值,用于唯一标识这个执行计划。

  2. Id: 操作的标识符,表示执行计划中每个操作的顺序。

  3. Operation: 执行计划中的操作类型。这里包含了一些操作,如SELECT STATEMENT(选择语句)、HASH JOIN(哈希连接)和TABLE ACCESS FULL(全表扫描)等。

  4. Name: 操作所涉及的表或索引的名称。

  5. Rows: 操作返回的行数估计。

  6. Bytes: 操作返回的数据字节数估计。

  7. Cost (%CPU): 操作的成本估计和 CPU 使用百分比。这是一个估算值,表示执行这个操作的开销。

  8. Time: 操作预计的执行时间。

Id 0-7是执行的序号,缩进最多的最先执行,缩进一样,上面的先执行。

cost越大的说明花费时间越久。

table access full说明还是全表扫描

但是查看sql的执行计划,发现还是全表扫描,那就是索引失效了。

`where EI.DEL_FLAG = '0' and EI.USE_STATUS != '已报废'` != 会导致USE_STATUS索引失效,于是我改成:

where EI.DEL_FLAG = '0' and EI.USE_STATUS = '新品' or EI.USE_STATUS = '发料中' or EI.USE_STATUS = '送修中' or EI.USE_STATUS = '调拨中'
  or EI.USE_STATUS = '发料中' or EI.USE_STATUS = '库存中' or EI.USE_STATUS = '借料中' or EI.USE_STATUS = '故障' or EI.USE_STATUS = '在用'

但是好像还是没走索引,查了一下,发现如果查询的数据量超过表数据的30%,索引就会失效,因为全表扫还更快 。这样看加索引这个方案好像不适合当前的业务需求,因为这个接口查询的数据量大,索引根本就没用啊!!!

分区表

查询到分区表也能加快查询速度。

如果 EQUIPMENT_INFO 表非常大,考虑将其分成多个分区表。分区可以减轻查询时需要扫描的数据量。

分区策略:包括按日期范围、按某个特定列的值范围、按地理区域等划分数据。

按使用状态分区:

创建分区表:
CREATE TABLE "EQUIPMENT_INFO_co"
   (	
	"ID" NUMBER(20,0) NOT NULL ENABLE, 
	....

	"USE_STATUS" VARCHAR2(20) DEFAULT '在用', 

	.....
   ) 
PARTITION BY LIST ("USE_STATUS")
(
    PARTITION new_items VALUES ('新品'),
    PARTITION pending_scrapped VALUES ('待报废'),
    PARTITION in_repair VALUES ('送修中'),
    PARTITION in_transfer VALUES ('调拨中'),
    PARTITION in_issue VALUES ('发料中'),
    PARTITION in_stock VALUES ('库存中'),
    PARTITION on_loan VALUES ('借料中'),
    PARTITION faulty VALUES ('故障'),
    PARTITION in_use VALUES ('在用'),
    PARTITION scrapped VALUES ('已报废')
);
--查看分区数据,查看use_status是新品的
SELECT * FROM EQUIPMENT_INFO_co PARTITION (new_items);

--修改分区表
ALTER TABLE EQUIPMENT_INFO
MODIFY PARTITION BY LIST (USE_STATUS)
(
    PARTITION new_items VALUES ('新品'),
    PARTITION pending_scrapped VALUES ('待报废'),
    PARTITION in_repair VALUES ('送修中'),
    PARTITION in_transfer VALUES ('调拨中'),
    PARTITION in_issue VALUES ('发料中'),
    PARTITION in_stock VALUES ('库存中'),
    PARTITION on_loan VALUES ('借料中'),
    PARTITION faulty VALUES ('故障'),
    PARTITION in_use VALUES ('在用'),
    PARTITION scrapped VALUES ('已报废')
);

--修改分区表,并指定表空间
ALTER TABLE EQUIPMENT_INFO
MODIFY PARTITION BY LIST (USE_STATUS)
(
    PARTITION new_items VALUES ('新品') TABLESPACE PARTITION1,
    PARTITION pending_scrapped VALUES ('待报废') TABLESPACE PARTITION1,
    PARTITION in_repair VALUES ('送修中') TABLESPACE PARTITION1,
    PARTITION in_transfer VALUES ('调拨中') TABLESPACE PARTITION1,
    PARTITION in_issue VALUES ('发料中') TABLESPACE PARTITION1,
    PARTITION in_stock VALUES ('库存中') TABLESPACE PARTITION1,
    PARTITION on_loan VALUES ('借料中') TABLESPACE PARTITION1,
    PARTITION faulty VALUES ('故障') TABLESPACE PARTITION1,
    PARTITION in_use VALUES ('在用') TABLESPACE PARTITION1,
    PARTITION scrapped VALUES ('已报废') TABLESPACE PARTITION1
);

这个分区表搞半天感觉没啥用,

缓存数据

有一些常用的数据可以在项目启动的时候放到redis中,来提高数据查询的响应速度。

因为这个查询接口的数据是读多写少的场景,所以我打算把这个查询的数据在项目启动的时候就放到缓存中,然后在监测这张表,当数据发生变更的时候,更新缓存中的数据。

/**
     * 项目启动时,会执行PostConstruct注释的方法。将查询的数据缓存到redis中。
     */
    @PostConstruct
    public void init()
    {
        System.out.println("初始化数据==============");

        redisCache.setCacheMap(Constants.EQUIPMENT_TYPE_NUM_CACHE_KEY,equipTypeMapNum);
    
    }

将查询的数据放到缓存后,这个查询的数据直接到redis中取就好,这样查询的速度的确快了挺多。

然后数据库写个触发器,当那张表的数据发生变更使用UTL_HTTP向程序发送http请求接口,接口就会更新redis中的数据。

创建用于发送http请求的存储过程:

CREATE OR REPLACE PROCEDURE send_http_get_request (
    p_url IN VARCHAR2
) AS
  req utl_http.req;
  res utl_http.resp;
BEGIN
  -- 打开HTTP请求
  req := utl_http.begin_request(p_url, 'GET', 'HTTP/1.1');
  
  -- 设置请求头
  utl_http.set_header(req, 'Content-Type', 'application/json');
  
  -- 执行HTTP请求
  res := utl_http.get_response(req);
  
  -- 处理HTTP响应,例如提取响应内容
  -- res.status_code, res.reason_phrase, utl_http.get_response_text(res)
  
  -- 关闭HTTP请求
  utl_http.end_response(res);
EXCEPTION
  WHEN OTHERS THEN
    -- 处理异常
    NULL;
END;
-- 创建触发器,当EQUIPMENT_INFO表发生insert update delete变更时触发
CREATE OR REPLACE TRIGGER equipment_info_change_trigger2
AFTER INSERT OR UPDATE OR DELETE ON EQUIPMENT_INFO
FOR EACH ROW
BEGIN

  --发送http请求通知程序表变更
  -- 调用发送HTTP请求的存储过程
  send_http_get_request('http://127.0.0.1:8082/dataChange?tableName=equipment_info');
END;

但是这样还有个问题。如果你数据经常变更就不要这样做,或者你一次变更变更好几条也不要,因为数据变更一条就发送一个http请求,程序更新一次。这样就会导致http请求接口变的非常慢,因为会有很多个请求同时过来,因为这个更新数据的接口本来就执行的慢(要查询数据,更新redis中的数据),你如果很多变更,会卡死程序,别我我为啥呜呜呜。

这边Oracle发送http请求会有个问题

创建或更新访问控制列表(ACL): 在Oracle数据库中,需要配置访问控制列表 (ACL),以允许数据库用户或角色访问外部网络资源。在这里,我们将创建一个新的 ACL。

BEGIN
  DBMS_NETWORK_ACL_ADMIN.create_acl (
    acl           => 'your_acl_name.xml',
    description   => 'ACL for UTL_HTTP',
    principal     => 'YOUR_DATABASE_USER', -- 替换为实际的数据库用户
    is_grant      => TRUE,
    privilege     => 'connect',
    start_date    => NULL,
    end_date      => NULL
  );

  DBMS_NETWORK_ACL_ADMIN.assign_acl (
    acl         => 'your_acl_name.xml',
    host        => '127.0.0.1', -- 替换为实际的目标主机
    lower_port  => NULL,
    upper_port  => NULL
  );

  COMMIT;
END;

授权访问: 确保你的数据库用户拥有执行 UTL_HTTP 包中的过程和函数的权限。你可以使用以下命令为用户授权:

GRANT EXECUTE ON UTL_HTTP TO YOUR_DATABASE_USER; -- 替换为实际的数据库用户

批量请求返回数据

最终方案

以上的做法虽然都是方案,但是都有一定缺点,而且是影响挺大的。后面想了想,既然一次性返回这么多数据会慢,那就批量返回。

让前端第一次发送这个查询接口的时候,请求1000条数据回去先展示后,接着马上又发请求请求10000条数据,如果返回的数据条数少于10000,就不要继续发了,反之继续发。这样用户就不会感受到慢,1000先给他看,后面的数据继续发。这样客户就是无感的,不会感受到慢。其实就是隐式分页。

有哪位看到这给指点,看有没有更好的方案。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD1602显示整数、小数应用

基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD1602显示整数、小数应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD1602简单介绍IIC通信简单介绍…

Android UiAutoMatorViewer打不开

UIAutoMatorViewer是个很好用的工具,能解析出任意手机页面的UI树,非常方便。 工具位置:SDK\tools\bin\uiautomatorviewer.bat 一般双击就能打开。 但有时会打不开,双击后无反应,在cmd窗口中运行也是如此。 这种情况…

python用YOLOv8对图片进行分类

用yolov8的模型进行分类 先上效果图 图片资源 模型下载地址 https://github.com/ultralytics/ultralytics 代码 import matplotlib.pyplot as plt from ultralytics import YOLO from PIL import Image import cv2model YOLO(../ultralytics/yolov8n.pt)# print(model…

【NodeJS】 API Key 实现 短信验证码功能

这里使用的平台是 短信宝整体来讲还是挺麻烦的平台必须企业才行,个人是无法使用该平台的 平台必须完成 身份信息认证 和 企业认证 这里就需要 “营业执照”了 ,没有 “营业执照” 的朋友还是后退一步吧 后端我用的是NodeJS ,使用第三方库 ro…

连接mysql 出现can‘t connect to server on ‘localhost‘ (10061) 报错

首先确保你自己已经安装了mysql。 如果安装了mysql 还是有问题。我们可以在 任务管理器 》服务 中找Mysql服务。 如果有Mysql 服务,启动服务即可。 如果没有这个服务,需要我们下载服务。具体操作如下 管理员启动终端,找到安装的mysql &…

转战MySQL Shell!数据库备份新姿势,轻松搞定备份操作!

MySQL8.0后续版本中主推使用MySQL Shell进行相关日常管理及维护操作,如果后续移除了mysqldump等命令后,如何进行数据库备份等相关操作呢?本文开始进行数据库备份的操作。 1. MySQL Shell 安装 1.1 下载 可以在MySQL官网进行下载&#xff0…

分布式系统:CAP 定理

欢迎来到分布式系统系列。在本文中,我们将学习并理解什么是 CAP 定理。CAP 代表一致性、可用性和分区容错性。当我们谈论CAP定理时,我们主要谈论的是分布式系统。首先,让我们了解一下什么是分布式系统。分布式系统是由运行在单台或多台机器上…

【驱动】串口驱动分析(一)-软件架构

区分不同的终端类型 串行端口终端(/dev/ttySn) 串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。 有段时间这些串行端口设备通常被称为终端设备&#xff0…

Redis哈希对象(listpack介绍)

哈希对象的编码可以是ziplist或者hashtable。再redis5.0版本之后出现listpack,为了是代替ziplist。 一. 使用ziplist编码 ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序都会先将保存了键值对的键…

el-table实现动态表头

1.1el-table渲染 <el-tableref"refreshTable":data"tableData"highlight-current-row><el-table-columnfixedwidth"170px"label"测点"align"center"prop"测站名称"/><el-table-column label"…

万户ezOFFICE wpsservlet任意文件上传漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品&#xff0c;统一的基础管理平台&#xff0c;实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台&#xff0c;将外网信息…

位图和布隆过滤器(C++)

位图和布隆过滤器 一、位图1. 引入2. 概念3. 代码实现setreset完整代码 4. 位图的应用 二、布隆过滤器1. 引入2. 概念3. 逻辑结构4. 特点5. 代码实现6. 布隆过滤器的应用 三、哈希切割 一、位图 1. 引入 当面对海量数据需要处理时&#xff0c;内存不足以加载这些数据&#xf…

C语言二叉树与堆的实现(一)

目录 二叉树 二叉树的分类&#xff08;目前只谈两种&#xff09; 满二叉树 完全二叉树 二叉树的性质&#xff08;其余的可以自己总结&#xff09; 选择练习 二叉树的存储结构 顺序存储方式 链式存储方式 一种完全二叉树&#xff1a;堆 堆的概念 堆的性质 建堆的时…

Windows本地搭建Emby媒体库服务器并实现远程访问「内网穿透」

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

OpenCvSharp从入门到实践-(06)创建图像

目录 1、创建图像 1.1实例1-创建黑色图像 1.2实例2-创建白色图像 1.3实例3-创建随机像素的雪花点图像 2、图像拼接 2.1水平拼接图像 2.2垂直拼接图像 2.3实例4-垂直和水平两种方式拼接两张图像 在OpenCV中&#xff0c;黑白图像其实就是一个二维数组&#xff0c;彩色图像…

GCN01——Ubuntu中设置vivado编辑器为vscode

确定vscode位置 在命令行中输入 which code得到文件地址 进入文件夹后可看到&#xff0c;这是个链接文件&#xff0c;不过无所谓&#xff0c;就用这个地址就行 设置Text Editor 打开setting选择右侧text editor 这里说明了如何进行设置 将自己的地址加进去就行 /usr/share…

Springboot快速整合kafka

kafka的基本了解 kafka也是 目前常用的消息中间件,支持同步与异步通信,和rabbitmq一样,工作模式大概相同,并且被spingboot整合的后的都是 中间件Template的实列化客户端类 ,消费者监听注解为KafkaListener,和RabbitListener和很相似,这些消息中间件使用过后,发现大致都是相同的…

SS6811H38V/1.6A 两通道 H 桥驱动芯片

SS6811H 为舞台灯光和其它电机一体化应用 提供一种双通道集成电机驱动方案。SS6811H 有 两路 H 桥驱动&#xff0c;每个 H 桥可提供最大输出电流 1.6A (在 24V 和 Ta 25C 适当散热条件下)&#xff0c;可驱 动两个刷式直流电机&#xff0c;或者一个双极步进电机&#xff0c;或 …

【shell】shell脚本编程作业

1 编写bash脚本&#xff0c;要求用户输入源目录和目标目录(绝对路径&#xff09;&#xff0c;然后列出源目录下所有的文件&#xff0c;并将这些文件拷贝到目标目录&#xff0c;并在文件名后面加上时间戳。&#xff08;提交源代码和运行截图&#xff09; if [ -d $1 ] || [ -d…

The Sandbox 携手 Sandsoft,与 Nuqtah 合作推动沙特阿拉伯的 Web3 发展

新的合作伙伴关系将增强创作者的能力&#xff0c;促进区块链生态系统的包容性。 The Sandbox 及其合作伙伴 Sandsoft 是移动游戏开发商和发行商&#xff0c;也是 AAA 人才驱动的投资者&#xff0c;他们非常高兴地宣布与 Nuqtah 建立新的合作伙伴关系&#xff0c;Nuqtah 是中东和…