生产慎用之调试日志对空间矢量数据批量插入的性能影响-以MybatisPlus为例

目录

前言

一、一些缘由

1、性能分析

二、插入方式调整

1、批量插入的实现

2、MP的批量插入实现

3、日志的配置

三、默认处理方式

1、基础程序代码

2、执行情况

四、提升调试日志等级

1、在logback中进行设置

2、提升后的效果

五、总结


前言

        在现代软件开发中,性能优化是一个永恒的话题,尤其是在处理大规模数据时,如何提升数据库操作的效率成为了一个关键问题。在数据库操作中,批量插入空间矢量数据是一个常见的需求,尤其是在地理信息系统(GIS)和空间数据分析领域。调试日志是软件开发中不可或缺的工具,它帮助开发者追踪程序的运行状态,定位问题和异常。然而,日志记录本身是一个资源密集型的操作,尤其是在生产环境中,过多的日志记录可能会对性能产生负面影响。对于空间矢量数据的批量插入操作,这种影响尤为明显,因为这类操作通常涉及大量的I/O操作和数据库交互。

        尽管调试日志对于开发和问题排查至关重要,但在生产环境中,它们可能会成为性能瓶颈。每次日志记录都会涉及到I/O操作,这会占用CPU时间和磁盘I/O资源。在批量插入空间矢量数据时,如果日志记录过于频繁,可能会导致以下问题:

  1. 降低I/O效率:日志记录会占用磁盘I/O资源,这可能会与数据库操作竞争资源,导致整体性能下降。
  2. 增加延迟:日志记录可能会引入额外的延迟,尤其是在高并发情况下,这会直接影响到批量插入操作的响应时间。
  3. 资源竞争:日志记录和数据库操作可能会竞争有限的系统资源,如CPU和内存,这可能会导致性能瓶颈。

        调试日志是一把双刃剑,它在帮助开发者解决问题的同时,也可能对生产环境的性能产生影响。在处理空间矢量数据的批量插入时,合理控制和优化日志记录是提升性能的关键。本文通过在MybatisPlus中调整插入SQL的输出对比前后的耗时与内存的占用,最大限度地减少对性能的负面影响。本文将深入探讨这些策略的具体实现和最佳实践,以期为Java开发者提供实用的指导和建议。

一、一些缘由

        其实在日常工作当中,空间矢量数据的数据量都是非常大。不仅是范围大,属性数据也尤其多,不仅属性列多,而且数据行数也可能非常多。那么我们在使用ORM框架在操作这些数据的时候,在进行空间数据入库的时候尤其需要注意性能的影响。有一些程序需要追求高性能,尤其是一些需要快速计算的场景,用户需要尽快的将数据入库,好开展后续的业务。

        之前有一个朋友给发了私信,说他们在处理线上的生产数据时,数据的规模大约是几十W的规模。在使用GeoTools读取Shapefile后,然后调用Mybatis-Plus来进行数据入库。它的整体性能不高,耗时比较久,然后就找到博主聊了一下。原始聊天截图就不放出来了。分享其中遇到的一些问题:

        1、这位朋友在进行批量数据入库的时候,使用循环来进行调用,没有使用批量操作。

        2、系统的日志级别开的比较低,为了方便监控程序,系统的日志级别在生产环境上也是Debug。

        3、服务器在空间数据库入库时,内存占用较高。

1、性能分析

        在了解了一些程序的执行细节之后,我也做了一个对照实验。实验的主要目的是对比应用程序中调试日志的输出对性能影响 ,主要方法就是在程序执行时打开和关闭系统日志,通过观察打开前后的应用程序执行消耗时间和使用Java VisualVM监控的CPU和内存消耗情况来对比。

二、插入方式调整

        为了首先将应用程序的插入调整到一个比较好的执行状态,我们先把原来的循环插入的方式进行了修改,改成批量插入的形式。因此这里有必要对批量插入的具体实现进行一个简单的介绍。

1、批量插入的实现

        在我们的代码中,使用的ORM框架是Mybatis-Plus,熟悉这个框架的小伙伴们一定知道。在MP中除了有单个插入的方法,还提供了一个批量插入的实现。因此,如果您是使用了MP这种的增强框架,那么改造起来还是比较快的,否则就需要大家自己去实现批量插入的方法。在MP中需要调用service提供的saveBatch(List,Size)即可。在在我的示例代码中,实现批量插入的关键代码如下所示:

Long s3 = System.currentTimeMillis();
if(dataList.size() >0) {placeService.saveBatch(dataList, 600);
}
Long e3 = System.currentTimeMillis();
System.out.println("空间入库耗时::"+ (e3 - s3) + "毫秒");

2、MP的批量插入实现

        上一节中对Mp的批量插入的方法进行了调用,这里我们依然对saveBatch方法进行简单的介绍,好让大家对saveBatch有一个直观的印象。我们可以打开ServiceImpl的实现类中的以下代码:

/*** 批量插入* @param entityList ignore* @param batchSize  ignore* @return ignore*/@Transactional(rollbackFor = Exception.class)@Overridepublic boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));}

        这里的方法表示首先获取sql的Statement对象,然后调用批量执行的方法。被调用的方法如下:

/*** 执行批量操作** @param entityClass 实体类* @param log         日志对象* @param list        数据集合* @param batchSize   批次大小* @param consumer    consumer* @param <E>         T* @return 操作结果* @since 3.4.0*/
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {Assert.isFalse(batchSize < 1, "batchSize must not be less than one");return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {int size = list.size();int idxLimit = Math.min(batchSize, size);int i = 1;for (E element : list) {consumer.accept(sqlSession, element);if (i == idxLimit) {sqlSession.flushStatements();idxLimit = Math.min(idxLimit + batchSize, size);}i++;}});
}

        来看一下insert方法的处理逻辑,最终的执行update的方法如下:

@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);final BoundSql boundSql = handler.getBoundSql();final String sql = boundSql.getSql();final Statement stmt;if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);// fix Issues 322BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);    // fix Issues 322currentSql = sql;currentStatement = ms;statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}handler.batch(stmt);return BATCH_UPDATE_RETURN_VALUE;}

        当然大家在使用这个类的时候还是非常方便的,只要调用相应的方法即可实现分批导入。

3、日志的配置

        在实例的应用开发过程中,日志的输出与管理,我们使用Logback组件。在最开始的时候,在对比实验中,首先我们采用默认的方式,即对应的ORM处理组件中的日志级别使用默认方法。具体如何在Logback中进行日志的设置,请大家结合互联网相关资料进行查询,这些都是比较成熟的。

三、默认处理方式

        对比实验的第一种实现方法就是采用默认的方法,即使用默认的日志级别。但是在最开始时,我们还是给出测试的代码的全部。如果您也感兴趣,可以替换相应的文件来进行验证这个过程。测试结果可能随着数据量的不同,数据属性字段的不同而有所不同。

1、基础程序代码

        为了还原网友提出的问题,也能尽快的找到原因。我们这里就以之前的全球主要城市为例,重点讲解如何进行数据的处理和融合,以及最终如何进入到数据库中。实例代码如下:

@Test
/*** * @throws Exception*/
public void shp2PostGIS() throws Exception {Long startTime = System.currentTimeMillis();File file = new File(SHP_FILE);if (!file.exists()) {System.out.println("文件不存在");}ShapefileDataStore store = new ShapefileDataStore(file.toURI().toURL());store.setCharset(Charset.defaultCharset());// 设置中文字符编码// 获取特征类型SimpleFeatureType featureType = store.getSchema(store.getTypeNames()[0]);CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();Integer epsgCode = CRS.lookupEpsgCode(crs, true);List<HashMap<String, Object>> mapList = new ArrayList<HashMap<String,Object>>();ModelMapper modelMapper = new ModelMapper();//设置忽略字段PropertyMap<HashMap<String,Object>, Ne10mPopulatedPlaces> propertyMap = new PropertyMap<HashMap<String,Object>, Ne10mPopulatedPlaces>() {protected void configure() {skip(destination.getPkId());}};modelMapper.addMappings(propertyMap);//忽略大小写modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);// 设置命名约定,将下划线转换为驼峰modelMapper.getConfiguration().setSourceNameTokenizer(NameTokenizers.UNDERSCORE).setDestinationNameTokenizer(NameTokenizers.CAMEL_CASE);//设置忽略模式modelMapper.getConfiguration().setSkipNullEnabled(true);Long s1 = System.currentTimeMillis();List<Ne10mPopulatedPlaces> dataList = new ArrayList<Ne10mPopulatedPlaces>();SimpleFeatureSource featureSource = store.getFeatureSource();// 执行查询SimpleFeatureCollection simpleFeatureCollection = featureSource.getFeatures();SimpleFeatureIterator itertor = simpleFeatureCollection.features();// 遍历featurecollectionwhile (itertor.hasNext()) {HashMap<String, Object> map = new HashMap<String, Object>();SimpleFeature feature = itertor.next();Collection<Property> p = feature.getProperties();Iterator<Property> it = p.iterator();// 遍历feature的propertieswhile (it.hasNext()) {Property pro = it.next();if (null != pro && null != pro.getValue()) {String field = pro.getName().toString();String value = pro.getValue().toString();map.put(field, value);}}// 获取空间字段org.locationtech.jts.geom.Geometry geometry = (org.locationtech.jts.geom.Geometry) feature.getDefaultGeometry();// 创建WKTWriter对象WKTWriter wktWriter = new WKTWriter();// 将Geometry对象转换为WKT格式的字符串String wkt = wktWriter.write(geometry);String geom = "SRID=" + epsgCode +";" + wkt;//拼接srid,实现动态写入map.put("geom", geom);mapList.add(map);}Long e1 = System.currentTimeMillis();System.out.println("解析shp:"+ (e1 - s1) + "毫秒");Long s2 = System.currentTimeMillis();for(HashMap<String, Object> map : mapList) {Ne10mPopulatedPlaces places = modelMapper.map(map, Ne10mPopulatedPlaces.class);dataList.add(places);}Long e2 = System.currentTimeMillis();System.out.println("转化shp:"+ (e2 - s2) + "毫秒");store.dispose();System.out.println(dataList.size());Long endTime = System.currentTimeMillis();Long time = endTime - startTime;System.out.println("程序运行耗时:"+ time + "毫秒");Long s3 = System.currentTimeMillis();if(dataList.size() >0) {placeService.saveBatch(dataList, 600);}Long e3 = System.currentTimeMillis();System.out.println("空间入库耗时::"+ (e3 - s3) + "毫秒");
}

        在不关闭执行SQL日志的情况,我们来看一下它的相关性能指标。

2、执行情况

        在默认情况下,这段程序在执行过程中会输出大量的调试日志,如下图所示:

        在我们的控制台中有许多的插入日志,同时可以看到,整个空间数据入库的时间为29512毫秒,即将近30秒。除了时间的消耗,我们再来看一内存方面的消耗。

        可以很直观的看到,在进行大量的日志输出时,内存的使用量还是比较大,同时根据不同的批次呈现一个比较有规律的上升和下降,而最大的内存使用接近1000MB左右。 接下来,我们再来看一下关闭日志输出后的效果。

四、提升调试日志等级

        为了实现在运行时将这个插入SQL的日式调试等级,我们在Logback中进行相应的配置。以此来验证在提升SQL调试日志的等级后,这个批量插入的方法是不是有一个性能的提升。

1、在logback中进行设置

        在系统中,我们采用logback来进行日志的配置,因此我们首先需要在logback中进行相应的设置。将日志的级别从debug提升到error,只有在发生错误的时候才进行输出。设置的关键代码如下所示:

<!--  Ne10mPopulatedPlacesMapper 关闭调试日志 add by 夜郎king in 2024-11-26  -->
<logger name="com.yelang.project.extend.earthquake.mapper.Ne10mPopulatedPlacesMapper" level="error"/>

请注意,这里的com.yelang.project.extend.earthquake.mapper.Ne10mPopulatedPlacesMapper标识我们需要关闭的ORM类的全名。我们将他的日志级别提升到了error。

2、提升后的效果

        在将输出日志关闭之后,在控制台中首先就没有了sql的调试日志,说明配置成功。

        可以看到控制台很干净,调试的SQL日志已经被清理掉。同时注意耗时情况,变成了7046,也就是7秒的时间就完成了处理。在来后台看一下是不是真的处理成功。在数据库进行相应的数据查询。

        可以看到,数据的总条数也是7342条。因此可以判断,关闭sql调试日志后,对时间的消耗降低了很多,从30秒优化到了7秒, 大概提升76%;再来看一下内存的占用情况。

        相对于默认的处理情况而言,提升了日志等级的处理方式,其内存占用更加平稳,波动小。同时最大的内存占用在700MB左右,更多是500MB以下。从侧面也说明了优化的效果。

五、总结

         以上就是本文的主要内容,本文通过在MybatisPlus中调整插入SQL的输出对比前后的耗时与内存的占用,最大限度地减少对性能的负面影响。文章通过对照实验,对比了开启调试日志和关闭调试日志后的数据插入性能,从对比实验结果可以看到。关闭调试日志后,我们的应用程序耗时更短,同时内存的占用也更低。如果在生产环境中进行使用,尤其是新手同志,为了观察参数就留下了很多调试信息,这样反而加大了系统的负担。所以要请大家一定综合理性的评估,关闭不必要的调试日志,让应用程序的性能最大。行文仓库,定有许多不足之处,如有不足,在此恳请各位专家在评论区批评指出,不胜感激。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Linux下编译安装METIS

本文记录Linux下编译安装METIS的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1 一、安装依赖 1.1 下载GKlib sudo apt-get install build-essential sudo apt-get install cmake 2.2 编译安装GKlib 下载GKlib代码&#xff0c; …

数据链路层总结

- - 链路、物理链路&#xff1a;两节点间物理线路&#xff08;有线、无线&#xff09;&#xff0c;中间没有任何其他的交换节点 数据链路、逻辑链路&#xff1a; 链路 协议需要的硬件、软件 网络适配器(网卡)&#xff1a;包含物理层、数据链路层 网络适配器软件驱动程…

基于Java和Vue开发的漫画阅读软件漫画阅读小程序漫画APP

前景分析 受众广泛&#xff1a;漫画的受众群体广泛&#xff0c;不仅限于青少年&#xff0c;还涵盖了成年人等多个年龄层和社会阶层。漫画文化在全球范围内的影响力不断扩大&#xff0c;未来漫画软件创业可以考虑全球市场的拓展。 市场需求大&#xff1a;数字化阅读趋势下&…

LoRa无线空调计费系统都应用在哪里

中央空调计费系统由于布线方式需要消耗大量的人力及成本&#xff0c;LoRa在楼宇自控及智能家居中的应用越来越广泛&#xff0c;成为当前普遍应用的通信技术。 LoRa模块无线传输技术的不断完善&#xff0c;逐步解决了温控器通信方面布线困难、施工成本高的问题&#xff0c;促进…

4.STM32通信接口之SPI通信---硬件SPI的介绍

上一节&#xff0c;我们学会软件的SPI&#xff0c;本节&#xff0c;我们将学习STM32的SPI硬件收发电路&#xff0c;虽然STM32的硬件收发电路很强大&#xff0c;但是&#xff0c;很多我们都用不到&#xff0c;我们只需会最基本的就可以。硬件的好处就是稳定&#xff0c;功能模块…

Open AI 推出 ChatGPT Pro

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

洛谷P1030 [NOIP2001 普及组] 求先序排列(c嘎嘎)

题目链接&#xff1a;P1030 [NOIP2001 普及组] 求先序排列 - 洛谷 | 计算机科学教育新生态 题目难度&#xff1a;普及 解题思路&#xff1a;这道题和之前做过的一道题很像&#xff0c;举一反三就行 相似题目&#xff1a;P1827 [USACO3.4] 美国血统 American Heritage - 洛谷 |…

创意型广告如何配音梨花声音研修院退费

张弛播音5天训练营靠谱吗&#xff0c;在当今竞争激烈的广告市场中&#xff0c;创意型广告以其独特的构思和表现形式脱颖而出。而配音作为广告的重要组成部分&#xff0c;对于创意型广告的成功起着至关重要的作用。 在为创意型广告配音之前&#xff0c;首先要深入理解广告的创意…

探索 Python 应用的分层依赖:解决 UOS 环境中的 libvirt-python 安装问题

探索 Python 应用的分层依赖&#xff1a;解决 UOS 环境中的 libvirt-python 安装问题 背景Python 版本升级 问题描述原因分析与解决方案 Python 应用的分层依赖&#xff1a;安装与部署的视角libvirt-python的分层依赖尝试的解决方案 使用编译好的 .whl 文件"嫁接"整个…

SpringBoot+ENC实现密钥加密及使用原理

?? 作者&#xff1a; ?? 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 ?? 社区&#xff1a; Java技术栈交流 ?? 主题&#xff1a; SpringBootENC实现密钥加密及使用原理 创作时间&#xff1a; 2024年06月23日 目录 前言1、整合SpringBoot 1.1、POM…

多源多汇流网络的等价转换与证明

多源多汇流网络的等价转换与证明 引言流的性质和定义推广转换方法等价性证明伪代码与C代码实现结论引言 在经典的流网络问题中,我们通常考虑的是单源单汇(即一个源节点和一个汇节点)的网络流。然而,在实际应用中,我们经常会遇到具有多个源节点和多个汇节点的情况。本文将…

如何制作“优美”PPT

目录 1.免费PPT模板网站&#xff1a; 2.免费有较好质量的图片网站&#xff1a; 免费图片资源 免费透明PNG图片资源&#xff1a; 免费icon图片资源&#xff1a; 3.选择好的图片&#xff1a; 图片底色 4.要与不要 千万不要&#xff1a; 一定要&#xff1a; 6.一些建议…

R中利用ggplot2绘制气泡图

闲来无事&#xff0c;整理了一下自己的绘图笔记&#xff0c;顺便分享到CSDN上。 一、介绍 气泡图&#xff08;Bubble Plot&#xff09;是一种常用的数据可视化方法&#xff0c;用于展示三个变量之间的关系。气泡图的特点是通过气泡的大小、颜色和位置来表达数据中的多维信息。…

腾讯新版滑块识别/滑块识别

最新的腾讯滑块也是进行了一小部分更新&#xff0c;滑块也变的非常千奇百怪。 之前写的处理图像的方法可能太粗糙&#xff0c;有的背景图无法识别&#xff0c;可以在模板匹配之前&#xff0c;加个图像处理。 with open(f"./img/sprite_{random_num}.png", "rb&…

Oracle系统性能监控工具oswatcher演示

1、关于 OSW OSWatcher 的使用符合 Oracle 的标准许可条款&#xff0c;并且不需要额外的许可即可使用&#xff01;&#xff01;&#xff01;&#xff01; OSWatcher (oswbb) 是一种 UNIX shell 脚本的集合&#xff0c;主要用于收集和归档操作系统和网络的度量&#xff0c;以便…

PowerShell install 一键部署postgres17

postgres 前言 PostgreSQL 是一个功能强大的开源对象关系数据库系统,拥有超过 35 年的积极开发经验 这为其赢得了可靠性、功能稳健性和性能的良好声誉。 通过官方文档可以找到大量描述如何安装和使用 PostgreSQL 的信息。 开源社区提供了许多有用的地方来熟悉PostgreSQL, 了…

Elasticsearch vs 向量数据库:寻找最佳混合检索方案

图片来自Shutterstock上的Bakhtiar Zein 多年来&#xff0c;以Elasticsearch为代表的基于全文检索的搜索方案&#xff0c;一直是搜索和推荐引擎等信息检索系统的默认选择。但传统的全文搜索只能提供基于关键字匹配的精确结果&#xff0c;例如找到包含特殊名词“Python3.9”的文…

【Qt在线安装器】不能下载Qt5

qt在线下载不显示以前的版本时&#xff1a; 勾选”Archive“&#xff0c;点击”筛选“ 然后就会显示出QT5的版本&#xff0c; 按流程下载即可

【Unity高级】如何获取着色器(Shader)的关键词

在动态设置Shader时&#xff0c;会需要通过EnableKeyword, DisableKeyword来完成。但一个Shader有哪些关键词呢&#xff1f;Unity的文档中并没有列出来&#xff0c;但我们可以通过遍历Shader的KeywordSpace来查看。 1. 代码如下 using UnityEngine;public class KeywordExamp…

1.1 Beginner Level学习之“使用 rosed 在 ROS 中编辑文件”(第九节)

学习大纲&#xff1a; 1. 使用 rosed rosed 是 ROS 自带的 Rosbash Suite 的一部分&#xff0c;它的目的是让你通过 ROS 包的名称快速编辑文件&#xff0c;而不用手动输入完整的路径&#xff0c;节省开发时间。 基本用法&#xff1a;$ rosed [package_name] [filename] 示例…