MyBatis批量插入之ON DUPLICATE KEY UPDATE多线程问题

807 阅读2分钟

首先,ON DUPLICATE KEY UPDATE很好用,只要建唯一键索引就可以和使用insertOrUpdateBatch方法一样快乐,并且还可以自定义更新的字段。

可是,真的这么好?

答案是不是,在多线程的情况下疯狂进行插入的时候,就会报错。

可能说不就是报错嘛,我进行数据补偿一下就是

但是!!!这个东西会导致死锁,锁表的,淦,不要问我为什么知道。。。。。

# mysql查看死锁

# mysql:Unknown error 1205/锁表 解决方法

出了问题,懂了吧

image.png

那需要再插入的时候进行非id唯一字段条件更新,怎么办?

先说我:

如果简单,真的让id就是此唯一字段,这种很快乐,用insertOrUpdateBatch就行

我现在处理是,代码里面先查询,再分出新增集合和修改集合,再分别进行批量新增和批量修改的方式进行

方案嘛,选自己喜欢的,不管怎么都要按自己情况取舍

方案:

1、你要不id就是此唯一字段,这种很快乐,用insertOrUpdateBatch就行

2、case when 这种方式实现的批量更新操作效率很低,而且,当更新的字段很多时,SQL语句会特别长。

<update id="updateByIds">  
    update tb_user  
    <trim prefix="set" suffixOverrides=",">  
        <trim prefix="name = case" suffix="end,">  
            <foreach collection="list" item="i" index="index">  
                when id= #{i.id,jdbcType=VARCHAR} then #{i.name,jdbcType=VARCHAR}  
            </foreach>  
        </trim>  
        <trim prefix="weight = case" suffix="end,">  
            <foreach collection="list" item="i" index="index">  
                when id= #{i.id,jdbcType=VARCHAR} then #{i.weight,jdbcType=DECIMAL}  
            </foreach>  
        </trim>  
        <trim prefix="high = case" suffix="end,">  
            <foreach collection="list" item="i" index="index">  
                when id= #{i.id,jdbcType=VARCHAR} then #{i.high,jdbcType=DECIMAL}  
            </foreach>  
        </trim>  
    </trim>  
    where id in  
    <foreach collection="list" item="item" open="(" close=")" separator=",">  
        #{item.id,jdbcType=VARCHAR}  
    </foreach>  
</update>

3、replace into 这个和ON DUPLICATE KEY UPDATE一样有问题的

<insert id="updateBatch" parameterType="java.util.List">
    replace into t_output_calendar (
		index, cal_date, user_type, create_time, modify_time, delete_flag
    ) values
    <foreach collection="list" item="item" index="index" separator=",">
        (
			#{item.index,jdbcType=INTEGER}, 
			#{item.calDate,jdbcType=TIMESTAMP}, 
			#{item.type,jdbcType=TINYINT}, 
			#{item.createTime,jdbcType=TIMESTAMP}, 
			#{item.modifyTime,jdbcType=TIMESTAMP}, 
			#{item.deleteFlag,jdbcType=TINYINT}
        )
    </foreach>
</insert>

4、写触发器,成本高、还慢

5、foreach成多条sql,一般般(注意:Mybatis映射文件中的sql语句默认是不支持以" ; " 结尾的,也就是不支持多条sql语句的执行。所以需要在连接mysql的url上加 &allowMultiQueries=true 这个才可以执行。)

<update id="updateBatch"  parameterType="java.util.List">  
    <foreach collection="list" item="item" index="index" open="" close="" separator=";">
        update tableName
        <set>
            name=${item.name},
            name2=${item.name2}
        </set>
        where id = ${item.id}
    </foreach>      
</update>

6、代码里面先查询,再分出新增集合和修改集合,再分别进行批量新增和批量修改的方式进行

(有一个问题,当你数据很多的时候,多到,你查询报错,记得建索引,加锁,哈哈哈)

(当然也有说使用原生jdbc,进行批处理+事务的,也可以试试)

7、存储过程操作,实现难度高(推荐gpt写,哈哈哈哈)

一种,数据进去了,再删除重复的

CREATE DEFINER=`root`@`localhost` PROCEDURE `proc1`()
BEGIN
		DECLARE retainId VARCHAR(64);

    DECLARE isAudit CHAR(1);

    DECLARE cur CURSOR FOR
        SELECT trade_no
        FROM or_jackyun
        GROUP BY trade_no, trade_type, trade_status
        HAVING COUNT(trade_no) > 1;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    OPEN cur;

    read_loop: LOOP
        FETCH cur INTO trade_no_val;
        IF done = 1 THEN
            LEAVE read_loop;
        END IF;

        SELECT 
               is_audit
        INTO 
             isAudit
        FROM or_jackyun
        WHERE trade_no = trade_no_val
        ORDER BY trade_no, create_time  ASC
        LIMIT 1;
				
				SELECT id INTO retainId FROM or_jackyun
        WHERE trade_no = trade_no_val
        ORDER BY trade_no, create_time  DESC
        LIMIT 1;

        -- 更新数据
        UPDATE or_jackyun
        SET is_audit = isAudit
        WHERE trade_no = trade_no_val
        ORDER BY trade_no, create_time DESC
        LIMIT 1;

        -- 删除数据
        DELETE FROM or_jackyun
        WHERE trade_no = trade_no_val and id not IN (retainId);
    END LOOP;

    CLOSE cur;
END

一种,查询、新增、修改全部都放在里面一起

CREATE DEFINER=`root`@`localhost` PROCEDURE `BatchInsertOrUpdateTradeNos`(
  IN trade_no_data JSON
)
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE total_records INT;
  -- 检查trade_no是否存在
      DECLARE rows_affected INT;
  -- 获取JSON数组中的记录总数
  SET total_records = JSON_LENGTH(trade_no_data);
  
  -- 循环处理每个记录
  WHILE i < total_records DO
      -- 获取JSON数据中的每个记录
      SET @record = JSON_UNQUOTE(JSON_EXTRACT(trade_no_data, CONCAT('$[', i, ']')));

      -- 解析JSON数据中的字段
      SET @trade_no = JSON_UNQUOTE(JSON_EXTRACT(@record, '$.trade_no'));
  			SET @id = JSON_UNQUOTE(JSON_EXTRACT(@record, '$.id'));
      SET @is_audit = JSON_UNQUOTE(JSON_EXTRACT(@record, '$.is_audit'));
     
      
  			
      SELECT COUNT(*) INTO rows_affected FROM or_jackyun WHERE trade_no = @trade_no;
      
      IF rows_affected > 0 THEN
          -- 存在,执行更新操作
          UPDATE or_jackyun
          SET 
              is_audit = @is_audit,
             
          WHERE trade_no = @trade_no;
      ELSE
          -- 不存在,执行插入操作
          INSERT INTO or_jackyun (id,trade_no, is_audit)
          VALUES (@id,@trade_no, @is_audit);
      END IF;
      
      SET i = i + 1;
  END WHILE;
END

调用存储过程(注意加@InterceptorIgnore(tenantLine = "1")注解)

@InterceptorIgnore(tenantLine = "1")
void callBatchInsertOrUpdateTradeNos(@Param("jsonData") String jsonData);

<!-- 调用存储过程的示例 -->
<select id="callBatchInsertOrUpdateTradeNos" statementType="CALLABLE">
   {call BatchInsertOrUpdateTradeNos(
           #{jsonData, mode=IN, jdbcType=VARCHAR, javaType=java.lang.String}
       )}
</select>

8、别玩多线程了,慢慢单线程吧哈哈哈哈哈

感谢:

# 为什么不建议使用ON DUPLICATE KEY UPDATE?

Mybatis——实现批量更新