批量操作DB

记录一次批量操作数据库,sqlServer服务器参数过多问题。

1.案例引入

对接另一个批发系统B,B需要把订单那些数据弄到系统A中。就是一个批量插入,批量更新的问题。系统保密需要,本文只做示例,具体代码都是模拟的。 (SpringBoot项目)

涉及到的操作类和数据库表

Order_master表【主订单表】

Order_detail表【自订单表,记录主订单里面各个商品品种购买详情,字段数量假设有30个】

Order实体类

OrderMapper

OrderSercice

假设一下批量采购了7500个商品,是不同的种类,

2.old方案

都知道是批量插入数据的吧,构建好数据之后,然后批量插入数据库里面就完事儿了。

//Service
public void insertFun(xxx) {
....... 构建数据
orderDetailMapper.insertOrderList(xxx);
}
<!-- xml -->
<insert id="insertOrderList" parameterType="orderDetail">
INSERT order_detail (xxxx)VALUES
<foreach collection="list" item="info" separator="," open="(" close=")">
#{info.1}, #{info.2}, #{info.3}, #{info.4}, #{info.5}......
</foreach>
</insert>

但是这样会有问题。数据库服务器会报错

究其原因: 因为sql server对参数有控制

上面的方法,最终的sql语句为

INSERT order_detail (xxxx)VALUES
(?, ?, ?, ?, ?, ............),
(?, ?, ?, ?, ?, ............),
(?, ?, ?, ?, ?, ............),
.....
(?, ?, ?, ?, ?, ............) -- 每个对象有30个参数,30 * 7500 = 225000 个参数。。肯定超过了其限制涩。

3.解决方案一

最扯淡的方案:

// service
public void insertFun(xxx) {
....... 构建数据
for ( int i = 0; i < size; ++i ) {
orderDetailMapper.insert(list.get(i));
}
}

确实可以解决2100参数的问题。。。。

靠谱一点儿的方案

2100 / 30 = 70 . 我每七十个为一个List,然后批量插入进入不就行了吗?

// service
public void insertFun(xxx) {
....... 构建数据
// 分批,每70个为一批次
List<List<OrderDetail>> ps = Lists.partition(list, 70);
for ( List<OrderDetail> t : ps ) {
orderDetailMapper.insertOrderList(t);
}
}

这样虽然解决了这个问题,但是,每一次循环伴随一次SqlSession的打开和关闭

有7500个商品,7500 / 70≈107。就意味着107SqlSession的打开和关闭,这个是很耗时间的呀。看来还是不能这样搞。

begin
INSERT order_detail (xxxx)VALUES
(?, ?, ?, ?, ?, ............),
......
(?, ?, ?, ?, ?, ............)
commit begin
INSERT order_detail (xxxx)VALUES
(?, ?, ?, ?, ?, ............),
......
(?, ?, ?, ?, ?, ............)
commit ......
begin
INSERT order_detail (xxxx)VALUES
(?, ?, ?, ?, ?, ............),
......
(?, ?, ?, ?, ?, ............)
commit

那我们就要想办法减少这个连接次数了。

4.saveBatch

然后发现了mybatis-plus的IService接口中的saveBatch。

我们点进去源码查看一下。

// IService.class==========================
public interface IService<T> {
@Transactional(rollbackFor = {Exception.class}) // 这里有这个注解哦。
default boolean saveBatch(Collection<T> entityList) {
return this.saveBatch(entityList, 1000); // 调用了这个saveBatch,可见默认size是1000
}
boolean saveBatch(Collection<T> entityList, int batchSize); // 就是这个
} //ServiceImpl.class==================
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
@Transactional(rollbackFor = {Exception.class})
public boolean saveBatch(Collection<T> entityList, int batchSize) {
// SqlMethod.INSERT_ONE是一个枚举
/*
INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>\nINSERT INTO %s %s VALUES %s\n</script>"),
*/
String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
// 继续向下执行这个, 第三个参数类型BiConsumer,接收两个参数,不返回东西
return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
sqlSession.insert(sqlStatement, entity);
});
} // 执行这个
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
// 调用了SqlHelpr的方法
return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
}
} //SqlHelper.class====================
public final class SqlHelper {
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", new Object[0]);
// 第三个参数Consumer。
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
int size = list.size();
int i = 1;
// 遍历实体对象的List,
for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
E element = var6.next();
consumer.accept(sqlSession, element); // 执行传过来的逻辑sqlSession.insert(sqlStatement, entity);
if (i % batchSize == 0 || i == size) { // 每一个批次执行一下。(这里是1000)
sqlSession.flushStatements();
}
} });
} public static boolean executeBatch(Class<?> entityClass, Log log, Consumer<SqlSession> consumer) {
....... sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); // 使用 `ExecutorType.BATCH`表示使用批量操作模式,MyBatis 会将多个 SQL 语句累积起来,而不是每执行一条 SQL 就与数据库交互。
if (!transaction) {
log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional");
}
boolean var7;
try {
consumer.accept(sqlSession); //---执行传过来的逻辑,就是上面的那个Consumer
sqlSession.commit(!transaction);
var7 = true;
} catch (Throwable var13) {
.......
} finally {
sqlSession.close();
} return var7;
}
}

从源码中可以发现saveBatch是下面这种情况的

open sqlsession........
begin
sqlSession.insert(sqlStatement, entity);
sqlSession.insert(sqlStatement, entity);
...
sqlSession.insert(sqlStatement, entity); // 1000个
sqlSession.flushStatements(); sqlSession.insert(sqlStatement, entity);
sqlSession.insert(sqlStatement, entity);
...
sqlSession.insert(sqlStatement, entity); // 1000个
sqlSession.flushStatements();
如此循环。。。。。 commit close sqlsession............

sqlSession.flushStatements()的作用

flushStatements() 方法主要用于批量操作时手动提交累积的 SQL 语句。当使用 ExecutorType.BATCH 时,所有的 SQL 语句会被缓存,直到你调用 flushStatements(),MyBatis 会将缓存中的 SQL 语句一次性提交给数据库执行。这个并不会提交事务哦

我们什么时候可以调用 flushStatements()

  • 批量大小控制:如果你的批量数据量比较大,可以控制每批次提交的数据量。通常情况下,合理的批量大小应该根据数据库和应用服务器的配置来调整,太大的批量会导致内存占用过高,太小的批量又不能有效利用批量操作的优势。
  • 减少内存压力:如果一次性积累的 SQL 语句过多,会占用大量内存,甚至导致内存超限错误。通过手动调用 flushStatements() 来减少内存的占用,每次提交一个小批次的 SQL 语句。我想这就是默认1000个的原因吧。批量操作时需要分批提交 SQL 语句,减少内存占用、提高性能

这样就可以批量插入数据,同时又避免了2100个参数的问题

this.saveBatch(整个list)

5.更快的?

结合数据库sqlserver有2100参数限制的问题,还有上面的saveBatch实际上是多条insert语句一起提交到数据库中的,我们可以想到一个更加优秀的解决方案。

  • 首先,把一条一条执行insert改为批量的
  • 同时,要注意每一条sql的参数限制
  • 仿照mybatis-plus源码大致思路执行

每行记录有30个字段,那么,2100/30 = 70,也就是说,要想办法将sql变成下面这个样子的

insert into table1 (x,x,x,....x) values
(x,x,x, .......),(x,x,x, .......),(x,x,x, .......)
(x,x,x, .......).....【70个】

那么,mapper.xml里面的东西可以先不变,就如同第2节里面的<foreach>标签拼接

下面给出实现大致细节。数据源配置,和maven依赖就不放了。

首先,mybatis-plus配置类

@Configuration
public class MybatisPlusConfig {
//SqlSessionFactory 是 MyBatis 会话工厂,负责创建和配置 SqlSession 实例。这样我们就可以拿到sqlsession了
@Bean
public SqlSessionFactory sqlSessionFactory(org.apache.ibatis.session.Configuration configuration) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setConfiguration(configuration);
factoryBean.setDataSource(dataSource()); // 注入数据源
return factoryBean.getObject();
} //SqlSessionTemplate 是 MyBatis 的 SqlSession 的封装,简化了事务的管理。
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory); // 创建 SqlSessionTemplate
}
}

serviceImpl类 手动控制

@Service
public class MyService {
@Autowired
private SqlSessionFactory sqlSessionFactory; @Autowired
private OrderDetailMapper orderDetailMapper; public void myBatchInsert(List<OrderDetail> details) {
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
OrderDetailMapper mapper = sqlSession.getMapper(OrderDetailMapper.class); List<List<OrderDetail>> pats = Lists.partition(details, 65); // 这里调小一丢丢
int batchCount = 0;
for (List<OrderDetail> tmp : pats ) {
mapper.insertOrderList(tmp);
batchCount++;
if ( batchCount % 16 == 0 ) { // 16 * 65 = 1040,一个批次插入1040个
sqlSession.flushStatements(); // 提交当前批次
}
}
sqlSession.commit(); // 提交事务,持久化到数据库
} catch(Exception e ) {
................
}
}
}

手动挡开着还是爽一些啊,。。

后面我还发现,有这样一个配置jdbc url参数上加rewriteBatchedStatements=true,这个就是把saveBatch由一条一条insert变成了一大条insert,但是一条insert语句太长了的话,sql解析起来估计会更慢了,还是要综合考虑sqlsession的打开与关闭次数sql数据库解析sql的速度,这三者,才能实现最快最高效的插入数据。 如何综合考虑,得出一个最优方案,估计只有不断测试了,因为每台服务器的配置都不相同。

批量操作DB的更多相关文章

  1. Magicodes.WeiChat——ASP.NET Scaffolding生成增删改查、分页、搜索、删除确认、批量操作、批量删除等业务代码

    关于T4代码生成这块,我之前写过几篇帖子,如:<Magicodes.NET框架之路——让代码再飞一会(ASP.NET Scaffolding)>(http://www.cnblogs.co ...

  2. EF中的那些批量操作

    在使用EF的过程中,我们经常会遇到需要批量操作数据的场景,批量操作有的时候不仅能提高性能,比如使用SqlBulkCopy进入批量插入的时候,而且比较方便操作,提高效率.那么这篇文章就来总结EF中的那些 ...

  3. Django ORM 中的批量操作

    Django ORM 中的批量操作 在Hibenate中,通过批量提交SQL操作,部分地实现了数据库的批量操作.但在Django的ORM中的批量操作却要完美得多,真是一个惊喜. 数据模型定义 首先,定 ...

  4. StackExchange.Redis学习笔记(四) 事务控制和Batch批量操作

    Redis事物 Redis命令实现事务 Redis的事物包含在multi和exec(执行)或者discard(回滚)命令中 和sql事务不同的是,Redis调用Exec只是将所有的命令变成一个单元一起 ...

  5. Mysqlutil.JDBCutil.Dtabaseutil数据库操作工具类[批量操作]

    一个用来操作数据库的常用工具类. 提供批量操作,生成建表,插入语句等 操作示例: // 1.获取连接 DataBaseUtil jdbc = new DataBaseUtil(); jdbc.getC ...

  6. python 全栈开发,Day115(urlencode,批量操作,快速搜索,保留原搜索条件,自定义分页,拆分代码)

    今日内容前戏 静态字段和字段 先来看下面一段代码 class Foo: x = 1 # 类变量.静态字段.静态属性 def __init__(self): y = 6 # 实例变量.字段.对象属性 # ...

  7. django中orm的批量操作

    ORM批量操作 数据模型定义 from django.db import models class Product(models.Model): name = models.CharField(max ...

  8. Azure Document DB Repository 的实现

    阅读 需要大约  5 分钟. 前景: Azure Cosmos DB 由 Microsoft 提供,是全球分布式多模型数据库. 通过 Azure Cosmos DB 跨任意数量的 Azure 地理区域 ...

  9. EF中的批量操作

    阅读目录 插入 更新 删除 在使用EF的过程中,我们经常会遇到需要批量操作数据的场景,批量操作有的时候不仅能提高性能,比如使用SqlBulkCopy进入批量插入的时候,而且比较方便操作,提高效率.那么 ...

  10. strak组件(10):批量操作

    效果图: 批量删除只是一个例子,可以根据需求定制自己想要的批量操作. 新增函数 def get_action_list(self) 钩子方法,获取要处理的批量操作的函数 def action_mult ...

随机推荐

  1. 在CLion中如何为CMakeLists.txt文件添加第三方依赖库

    cmake_minimum_required(VERSION 3.5)project(ImageBasedModellingEdu)set(CMAKE_MODULE_PATH "${CMAK ...

  2. LOL(英雄联盟) API 接口

    /*LOL(英雄联盟) API 接口 By wgscd /*LOL(英雄联盟) API 接口 By wgscd QQ:1009374598 */ GET https://127.0.0.1:58182 ...

  3. Linux 常用脚本命令-lsof、find、rpm、SS、top、vim

    1,关机命令 1 shutdown -h now/0 2 halt 3 init 0 4 poweroff 5 举例: 6 shutdown -h 3 ------3分钟后关机(可用shutdown ...

  4. 后端开发之chrome开发者模式-copy

    1. 场景描述 java开发前后端分离模式越来越流行,后端人员可以直接使用swagger进行接口调试(前后端分离之Swagger2),但是调试的时候,需要设置入参,假如该模块不是软件老王开发的,接别人 ...

  5. HBase-3rowkey的设计

    HBase表热点 1 什么是热点 检索habse的记录首先要通过row key来定位数据行. 当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求 ...

  6. kNN(K- Nearest Neighbor)基本原理

  7. Docker基础命令(安装和创建管理容器)

     docker ps -a 查看容器 docker inspect c008 使用 inspect 命令查看镜像详细信息,包括制作者.适应架构.各层的数字摘要等.

  8. Collection的子接口之一:List 接口

    List 接口概述: 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组 List集合类中 元素有序.且可重复,集合中的每个元素都有其对应的顺序索引. List容器中的元素都对 ...

  9. 一组开源、免费、Metro风格的 WPF UI 控件库 - MahApps.Metro

    前言 今天大姚给大家分享一个开源.免费.Metro风格的 WPF UI 控件库:MahApps.Metro. 项目介绍 MahApps.Metro 是一个开源.免费.Metro风格的 WPF UI 控 ...

  10. 开源数据库生态遇新变数,天翼云TeleDB提供企业数据管理更优解!

    近日,知名开源大规模并行处理 (MPP) 数据库Greenplum的源代码在其官方GitHub页面突然消失,引发了用户和开发者的广泛关注, PostgreSQL生态系统或将产生新变数.开源软件在面对商 ...