当前使用版本(必填,否则不予处理)

3.3.0

该问题是如何引起的?(确定最新版也有问题再提!!!)

重现步骤(如果有就写完整)

报错信息

Comment From: miemieYaho

来个复现demo

Comment From: angiely1115

从sql打印的日志来看,是单条插入。只是最后一次性事物提交。单条插入那里最耗时

Comment From: huanghuanghui

遇到同样的问题,看源码中的实现是一个foreach循环,saveBranch中集合有多少条数据就提交多少次,导致效率非常低

Comment From: huanghuanghui

使用官网推荐的p6spy进行SQL的日志分析,看到saveBranch方法其实是,就是封装了一个集合提交 MyBatis-Plus 3.30版本,saveBatch批量插入较慢,批量插入300多条数据,需要19s。

Comment From: angiely1115

但是有时候插入3000多条平均1s左右。这跟表的字段大小应该也有关系。我插入300条耗时19s,这张表字段比较多。

Comment From: miemieYaho

是有大字段吧?

Comment From: huanghuanghui

设置开启数据库批量提交&rewriteBatchedStatements=true,可以解决批量插入慢的问题

url: jdbc:mysql://127.0.0.1:3306/order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true

Comment From: angiely1115

已设置,不然批量的不会生效

Comment From: angiely1115

是有大字段吧?

没有大字段,就单独的字段比较多,有40多个。

Comment From: huanghuanghui

使用mybatis原生的《foreach》标签写下批量插入脚本,单元测试看看执行时间

Comment From: ListenBom

我也遇到了相同的问题,我使用的是mariadb驱动,mybatis-plus版本为3.3.2,在执行批量操作时会在: protected boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) ;方法内耗时较久,每次插入大概不到100条数据,耗时平均在10S左右。当我使用xml的foreach时插入时间恢复正常。 我的表结构: CREATE TABLE pd01eh ( id char(32) NOT NULL COMMENT 'ID', PD01ID char(32) NOT NULL COMMENT '借贷账户信息单元ID', PD01AI01 varchar(60) DEFAULT NULL COMMENT '账户编号', PD01ED01 varchar(60) DEFAULT NULL COMMENT '还款状态', PD01EJ01 varchar(60) DEFAULT NULL COMMENT '逾期(透支)总额', PD01ER03 varchar(60) DEFAULT NULL COMMENT '月份', create_time datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='历史表现信息'; 其中id为uuid。 我同事在使用相同版本时却没有出现相同的问题

Comment From: angiely1115

我也遇到了相同的问题,我使用的是mariadb驱动,mybatis-plus版本为3.3.2,在执行批量操作时会在: protected boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) ;方法内耗时较久,每次插入大概不到100条数据,耗时平均在10S左右。当我使用xml的foreach时插入时间恢复正常。 我的表结构: CREATE TABLE pd01eh ( id char(32) NOT NULL COMMENT 'ID', PD01ID char(32) NOT NULL COMMENT '借贷账户信息单元ID', PD01AI01 varchar(60) DEFAULT NULL COMMENT '账户编号', PD01ED01 varchar(60) DEFAULT NULL COMMENT '还款状态', PD01EJ01 varchar(60) DEFAULT NULL COMMENT '逾期(透支)总额', PD01ER03 varchar(60) DEFAULT NULL COMMENT '月份', create_time datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='历史表现信息'; 其中id为uuid。 我同事在使用相同版本时却没有出现相同的问题

请问你数据连接url有设置开启数据库批量提交&rewriteBatchedStatements=true这个吗?

Comment From: miemieYaho

字段越多 ognl需要处理的表达式越多,你40个字段排除id就有39个,那就要至少过39*2 次

Comment From: abc136609517

我觉得mp批量操作加个flag参数开关 flag=false关闭就使用单条sql执行 最后事务提交 flag=true开启就使用批量的sql操作(非单条) 这样我们在写的时候 可以自由选择 毕竟量少的走批量就行 mp考虑的是量非常大的情况所以使用单条操作 单条操作是每1000条做一次事物提交(也可以指定条数)

Comment From: huanghuanghui

这块是可以改进的,mybaits-plus使用的刷新SQL Session的方式批量提交,比起数据库批量执行速度相对慢很多。还有批量执行方法,都在方法上添加@Transaction注解,量比较大的时候,就会事务超时,事务应该是用户去控制的,而不能为用户去开启事务。

Comment From: suhengli

设置开启数据库批量提交&rewriteBatchedStatements=true,可以解决批量插入慢的问题

url: jdbc:mysql://127.0.0.1:3306/order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true

我postgresql设置了这个没有效果,他还是一条一条的insert。我用的是 sharding-jdbc-spring-boot-starter 3.1.0.M1

Comment From: ListenBom

我也遇到了相同的问题,我使用的是mariadb驱动,mybatis-plus版本为3.3.2,在执行批量操作时会在: protected boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) ;方法内耗时较久,每次插入大概不到100条数据,耗时平均在10S左右。当我使用xml的foreach时插入时间恢复正常。 我的表结构: CREATE TABLE pd01eh ( id char(32) NOT NULL COMMENT 'ID', PD01ID char(32) NOT NULL COMMENT '借贷账户信息单元ID', PD01AI01 varchar(60) DEFAULT NULL COMMENT '账户编号', PD01ED01 varchar(60) DEFAULT NULL COMMENT '还款状态', PD01EJ01 varchar(60) DEFAULT NULL COMMENT '逾期(透支)总额', PD01ER03 varchar(60) DEFAULT NULL COMMENT '月份', create_time datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='历史表现信息'; 其中id为uuid。 我同事在使用相同版本时却没有出现相同的问题

请问你数据连接url有设置开启数据库批量提交&rewriteBatchedStatements=true这个吗?

有设置此属性的。如果我没有设置此属性的话,xml插入应该也会比较慢吧?我的DB链接如下: jdbc:mariadb://127.0.0.1:3306/credit?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true

Comment From: dongbaibai

继承ServiceImpl,再重新把批量插入的方法重写一遍。。。

Comment From: suhengli

继承ServiceImpl,再重新把批量插入的方法重写一遍。。。

谢谢

Comment From: dongbaibai

刚才仔细的看了一下MP中saveBatch方法,他采用的是BatchExecutor来进行所有statement的提交,你有300多条数据,就会执行300多次的sql 至于Mybatis的xml中使用标签的话,其实是执行的一条sql。所以效率高些 我尝试去修改一下MP批量插入的方式,要是能成功的话我再把代码贴出来

Comment From: dongbaibai

自定义InsertBatch需要以下三步骤: 【RewriteSqlInjector】自定义SQL注入器 `/* * 为了在SQL注入的时候,注入自定义的InsertBatch方法 / @Component public class RewriteSqlInjector extends DefaultSqlInjector {

@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
    return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            new DeleteById(),
            new DeleteBatchByIds(),
            new Update(),
            new UpdateById(),
            new SelectById(),
            new SelectBatchByIds(),
            new SelectByMap(),
            new SelectOne(),
            new SelectCount(),
            new SelectMaps(),
            new SelectMapsPage(),
            new SelectObjs(),
            new SelectList(),
            new SelectPage(),
            new InsertBatch()
    ).collect(toList());
}

}`

【BatchMapper】自定义Mapper基类,作用与BaseMapper相似,需要实现InsertBatch的Mapper需继承该接口

public interface BatchMapper {

int insertBatch(@Param("list") Collection<T> entities);

}

*【InsertBatch】自定义批量插入的方法 ** /** * SQL注入的格式: * * CREATE TABLEuser( *idint NOT NULL AUTO_INCREMENT, *namevarchar(255) DEFAULT NULL, * PRIMARY KEY (id`) * ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; * * 批量生成的SQL * * insert into user (id,name) values * * (#{i.id},#{i.name}) * * / public class InsertBatch extends AbstractMethod {

private final String SQL = "<script>\nINSERT INTO %s %s VALUES %s\n</script>";

private final String METHOD = "insertBatch";

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
    KeyGenerator keyGenerator = new NoKeyGenerator();
    String columnScript = SqlScriptUtils.convertTrim(tableInfo.getKeyInsertSqlColumn(true) + tableInfo.getFieldList().stream().map(TableFieldInfo::getColumn).collect(joining(NEWLINE)),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
    String valuesScript = SqlScriptUtils.convertForeach(LEFT_BRACKET +
            SqlScriptUtils.convertTrim(getAllInsertSqlPropertyMaybeIf("i.", tableInfo), null, null, null, COMMA)
            + RIGHT_BRACKET, "list", null, "i", ",");
    String keyProperty = null;
    String keyColumn = null;
    // 表包含主键处理逻辑,如果不包含主键当普通字段处理
    if (StringUtils.isNotEmpty(tableInfo.getKeyProperty())) {
        if (tableInfo.getIdType() == IdType.AUTO) {
            /** 自增主键 */
            keyGenerator = new Jdbc3KeyGenerator();
            keyProperty = tableInfo.getKeyProperty();
            keyColumn = tableInfo.getKeyColumn();
        } else {
            if (null != tableInfo.getKeySequence()) {
                keyGenerator = TableInfoHelper.genKeyGenerator(tableInfo, builderAssistant, METHOD, languageDriver);
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            }
        }
    }
    String sql = String.format(SQL, tableInfo.getTableName(), columnScript, valuesScript);
    SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
    return this.addInsertMappedStatement(mapperClass, modelClass, METHOD, sqlSource, keyGenerator, keyProperty, keyColumn);
}

/**
 * 对属性添加prefix,并且不生成<if>标签
 * @param prefix
 * @param tableInfo
 * @return
 */
private String getAllInsertSqlPropertyMaybeIf(final String prefix, TableInfo tableInfo) {
    final String newPrefix = prefix == null ? EMPTY : prefix;
    return tableInfo.getKeyInsertSqlProperty(newPrefix, true) +
            tableInfo.getFieldList().stream().map(i ->
                    i.getInsertSqlProperty(newPrefix)).filter(Objects::nonNull).collect(joining(NEWLINE));
}

} `

Comment From: angiely1115

已经使用,谢谢

Comment From: dongbaibai

你是怎么解决的大兄弟

Comment From: angiely1115

你是怎么解决的大兄弟

就是按照你给的方式处理的呀

Comment From: dongbaibai

好吧,我以为你有其他的方法 这个能解决你的问题吗

Comment From: angiely1115

好吧,我以为你有其他的方法 这个能解决你的问题吗

可以,其实官方的也不是慢,主要是我的这种表太大,表字段过多。

Comment From: javaApprenticeHzk

MySQL的JDBC连接的url中要加rewriteBatchedStatements参数,并保证5.1.13以上版本的驱动,才能实现高性能的批量插入。 MySQL JDBC驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,批量插入实际上是单条插入,直接造成较低的性能。 只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL 另外这个选项对INSERT/UPDATE/DELETE都有效。 原先10万条数据25个字段8个大字节字段10秒完成

Comment From: Nymph2333

MySQL的JDBC连接的url中要加rewriteBatchedStatements参数,并保证5.1.13以上版本的驱动,才能实现高性能的批量插入。 MySQL JDBC驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,批量插入实际上是单条插入,直接造成较低的性能。 只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL 另外这个选项对INSERT/UPDATE/DELETE都有效。 原先10万条数据25个字段8个大字节字段10秒完成

之前在CSDN上看说是只对Insert生效,并且是大于3条的时候生效

Comment From: libra1010

BatchMapper

最新版使用此方式后,一直提示 nested exception is org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [list, param1] nested exception is org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [list, param1] 找了很久也没有找到原因

Comment From: Kang-Yang

最新版本,加上参数不生效,还是单条去执行