Mybatis批量插入問題&MySQL參數max_allowed_packet


1、背景:

  在做業務系統時,經常會碰到主子表模型,子表的數據量比較大,如果采用for循環進行insert操作,效率會很慢,MyBatis提供一個批量操作功能foreach,批量插入操作效率會大大提高。

<insert id="insertBatch" parameterType="java.util.List">
    <![CDATA[insert into bd_user (id, dept_id, user_code, user_name, birthday, usable) values ]]>
    <foreach collection="list" item="item" index="index" separator=",">
        <![CDATA[(#{item.id},#{item.deptId},#{item.userCode},#{item.userName},#{item.birthday},#{item.usable})]]>
    </foreach>
</insert>

  隨之而來,我們會有一個疑問,這個數據量有沒有極限呢,它會受哪些條件影響?帶着這樣的思考我們進行實驗,看看結果如何。

2、測試過程

  1)數據庫使用MySQL8.0.19,默認配置。數據使用虛擬機安裝,虛擬機配置為2核4G內存。

  2)數據庫表結構

CREATE TABLE `bd_user`  (
  `id` bigint(0) NOT NULL,
  `dept_id` bigint(0) NULL DEFAULT NULL COMMENT '部門ID',
  `user_code` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶編碼',
  `user_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶名稱',
  `birthday` date NULL DEFAULT NULL COMMENT '生日',
  `usable` tinyint(1) NULL DEFAULT NULL COMMENT '是否可用',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '人員' ROW_FORMAT = DYNAMIC;

  3)測試代碼

@SpringBootTest(classes = MybatisApplication.class)
public class UserTest {
    @Autowired
    private IUserOperateService userOperateService;
    @Autowired
    private SnowFlake snowFlake;

    @Test
    public void addUser() {
        int start = 0;
        int len = 10000;
        List<User> list = new ArrayList<>(len);
        for (int j = 0; j < len; j++) {
            User model = new User();
            model.setId(snowFlake.nextId());
            model.setDeptId((long) (1 + j % 4));
            model.setUserCode(StringUtils.leftPad((++start) + "", 10, "0"));
            model.setUserName("測試數據" + model.getUserCode());
            model.setUsable(Boolean.TRUE);
            model.setBirthday(new Date());
            list.add(model);
        }
        long startTime = System.currentTimeMillis();
        userOperateService.save(list);
//        for (User user : list) {
//            userOperateService.save(user);
//        }
        System.out.println("耗時:" + (System.currentTimeMillis() - startTime) + "毫秒");
    }
}

  4)實驗結果如下(每次操作后數據都會被清空)

記錄數 for方式耗時(毫秒) foreach方式耗時(毫秒)
第一次 第二次 第三次 第一次 第二次 第三次
1000 4909 4923 4327 890 860 879
5000 18196 18316 18633 1350 1200 1333
10000 -  - - 1782 1476 1398
100000 - - - 6567 4780 5288
500000       23691 22573 22128

  數據達到100W條記錄時,出現如下錯誤:

com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (99,899,527 > 67,108,864). You can change this value on the server by setting the 'max_allowed_packet' variable.

3、關於MySQL的max_allowed_packet參數

  1)參數說明

  • max_allowed_packet為數據包消息緩存區最大大小,單位字節,默認值67108864(64M),最大值1073741824(1G),最小值1024(1K),參數值須為1024的倍數,非倍數將四舍五入到最接近的倍數。
  • 數據包消息緩存區初始大小為net_buffer_length個字節
  • 每條SQL語句和它的參數都會產生一個數據包緩存區,跟事務無關。
  • 我嘗試調整該參數的大小,它並不能提高性能,它的作用在於能夠處理大參數,如大BLOB或長字符串就可能調整該參數,還有in后面的記錄數也受制於該參數。

  2)查看和設置max_allowed_packet參數

show variables like 'net_buffer_length';
show variables like 'max_allowed_packet';  // 查看參數
set global max_allowed_packet=536870912;   // 重新打開數據庫連接參數生效,數據庫服務重啟后參數恢復為默認,想持久化可以在my.cnf中設置該參數

  官網介紹:https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_allowed_packet

4、回到剛才的報錯

  剛才我們測試100W條數據報錯,如果我們把100W數據拆成2個50W條數據進行保存,則不會報錯,耗時大約為插入50條數據的的2倍。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM