MySQL批量插入的分析以及注意事項

目錄

  一、背景php

  二、兩種方式對比html

    2.一、一次插入一條數據mysql

    2.二、一次插入多條數據sql

  三、拓展一下數據庫

  四、Other網絡

 

一、背景

  咱們在工做中基本都會碰到批量插入數據到DB的狀況,這個時候咱們就須要根據不一樣的狀況選擇不一樣的策略。併發

  只要瞭解sql,就應該知道,向table中插入數據的命令,至少有insert和replace這兩種,使用哪種命令,和本身的業務有關;學習

  本文就針對insert進行批量插入進行闡述,而後根據自身經歷分享幾個注意事項。測試

 

二、兩種方式的對比

  即便是insert命令,他也是有多種插入數據的方式的。但這裏就不深刻了解底層insert是怎麼作的了,那個已經超出本人的知識範疇,哈哈。優化

  可是咱們能夠大體瞭解MySQL的執行命令時的初略步驟:

  一、首先創建鏈接(Socke鏈接);

  二、Client將要執行的sql命令經過TCP鏈接,發給Server;

  Client,能夠理解爲咱們用各類語言寫的項目程序(客戶端);

  Server,就是數據庫Server,負責執行。

三、數據庫Server收到數據(sql)後,會解析sql,而後進行處理;

四、將處理結果返回客戶端。

有了上面的流程,咱們就開始說insert的兩種插入方式區別,下面是測試使用的表:

CREATE TABLE `user` (
  `id` 		int(11) 	NOT NULL AUTO_INCREMENT COMMENT '編號',
  `name` 	varchar(40) NOT NULL COMMENT '姓名',
  `gender` 	tinyint(1) 	DEFAULT '0' COMMENT '性別:1-男;2-女',
  `addr` 	varchar(40) NOT NULL COMMENT '住址',
  `status` 	tinyint(1) 	DEFAULT '1' COMMENT '是否有效:1-有效;2-無效',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

 

2.一、一次插入一條數據

  最初學習數據庫,都知道使用insert能夠實現數據插入,好比向user表中插入一條數據:

mysql> insert into user (id, name, gender, addr, status) value (1, 'aaa', 1, 'beijing', 1);
Query OK, 1 row affected (0.00 sec)

mysql> select * from user;
+----+------+--------+---------+--------+
| id | name | gender | addr    | status |
+----+------+--------+---------+--------+
|  1 | aaa  |      1 | beijing |      1 |
+----+------+--------+---------+--------+
1 row in set (0.00 sec)

  這是最簡單的方式了,固然這是在命令行裏面,固然命令行也是一種客戶端;

  若是是客戶端咱們代碼的程序,好比Java利用jdbc來執行sql,也是傳給MySQL Server上面執行的insert命令;

  上面的insert命令的確是能插入數據數據的,也就是每執行一條insert命令,就須要經過網絡將命令發送給MySQL Server解析運行,若是有上千萬行數據須要插入,那麼是否是須要進行上千萬次鏈接傳輸呢?雖然如今可使用鏈接池,可是傳輸的次數是是躲不掉的。

  使用insert一次插入一條數據的這種方式,絕大多數都是使用這種方式,來進行少許的數據插入!!!

  若是用這種方式進行大量數據的入庫,哈哈,花的時間能夠喝好多杯咖啡了。

 

2.二、一次插入多條數據

  上面已經說到了,一次插入一條數據的主要缺陷是:須要創建N次鏈接,而後傳輸N鏈接,由於鏈接池的存在,能夠忽略鏈接耗時,可是傳輸N次的耗時,不可小覷,因此咱們能夠從這方面進行考慮優化。

  好比,一個工人負責將100塊磚從A點搬到B點,每次搬1塊磚,花費1個單位時間,那麼搬完100塊磚,須要100單位時間(不考慮來回);

  若是一次搬5塊磚,那麼只須要20單位時間,是否是快了不少呢?

  同理,咱們使用insert也能夠進行批量插入數據:

insert into user 
	(id, name, gender, addr, status) 
value 
	(2, 'bbb', 0, 'shanghai', 1),
	(3, 'ccc', 1, 'hangzhou', 0),
	(4, 'ddd', 0, 'chongqing', 0);

  這樣就能夠一次性插入3條數據了。

  對於客戶端來講,只須要進行拼接sql語句便可,而後將拼接後的sql一次性發給MySQL Server就能夠了。

  注意,SQL要使用拼接,而不是說預處理!!!

  預處理的做用是避免頻繁編譯sql、sql注入;使用預處理來進行批量插入時,使用循環每次設置佔位符值,這個和一次插入一條命令是等價的,以下面的示例,其實執行了3次1條記錄插入:

<?php
    $pdo = new PDO("mysql:host=localhost;dbname=test","root","root");
    $sql = "insert into user (id, name, gender, addr, status) values (?,?,?,?,?)";
    $stmt = $pdo->prepare($sql);

    $stmt->execute(array("5", "eee", "1", "PEK", 1));
    $stmt->execute(array("6", "fff", "0", "SHA", 0));
    $stmt->execute(array("7", "ggg", "1", "LNL", 1));
 ?>

  

  正確的方式:

<?php
    $pdo = new PDO("mysql:host=localhost;dbname=test","root","root");
    $sql = 'insert into user (id, name, gender, addr, status) values ';

    // 可使用循環進行sql拼接
    $sql .= '("5", "eee", "1", "PEK", 1),';
    $sql .= '("6", "fff", "0", "SHA", 0),';
    $sql .= '("7", "ggg", "1", "LNL", 1)'; 

    $pdo->exec($sql);
 ?>

  

  若是是Java可使用原生JDBC,進行上面同樣拼接,就不寫代碼了;

  若是Java使用Mybatis的話,可使用<foreach>標籤,

<insert id="batchInsert" parameterType="list">
    insert ignore into user (id, name, gender, addr, status) values
    <foreach collection="list" item="item" separator=",">
        (
	        #{item.id,jdbcType=INT}, 
	        #{item.name,jdbcType=VARCHAR}, 
	        #{item.gender,jdbcType=BIT},
	        #{item.addr,jdbcType=VARCHAR}, 
	        #{item.status,jdbcType=BIT}
        )
    </foreach>
</insert>

  

三、拓展一下

  批量insert,每次insert的量是多少合適呢?

  以上面工人搬磚的例子,一次搬5塊磚,須要20單位時間,那豈不是1次搬100塊磚,只須要1單位時間了?是這個邏輯,可是這樣是不行的,須要看實際狀況!!!

  這個實際狀況是什麼呢?很差說,好比一個比較強壯的工人,一次100塊磚,不是難事;若是工人沒那麼強轉,一次100塊磚,可能直接把工人給幹倒了,1塊磚也搬不了,這時可不止100單位時間。

  另外,放磚的B點,是否是能一次接收100塊磚,這也是一個問題。

 

  上面的例子,類比到insert批量插入,就須要注意:

  一、要根據狀況設置一次批量插入的數據量,數據量大,在網絡中傳輸的事件也越久,出現問題的可能也越大;

  二、除了網絡,還要看機器配置,MySQL Server配置差了,sql寫得再好,效率也不會過高;

  三、另外批量這個詞,是指一次插入多條數據,咱們除了要注意數據的條數,還要注意一條數據的大小,舉個例子:好比一條記錄的數據量有1M,10條記錄的數據量就10M,這時一次插100條,100M數據,嘿嘿,你試試看!!因此,一次插入多少數據,必定要通過屢次測試後再決定,別人1次插100條最優,你可能1次插10條才最優,沒有絕對的最優值(批量插入未必老是比單條插入效率高)。

  四、數據庫有個參數,max_allowed_packet,也就是每個包(sql)命令大小,默認是1M,那麼sql的長度大於1M就會報錯。你可能會說,我們把這個參數設成10M,100M不就好了???對呀,沒毛病,但你是DBA嗎?你有權限嗎?即便調大這一個參數,你要知道影響的可不止你這一張表,而是整個DB Server,那影響的但是不少庫,不少表。

  五、批量插入並非越快越好,咱們可能但願越快越好,這很正常,節省時間嘛。可是咱們必定要知道,數據庫分讀寫,有集羣,這就意味着,須要同步!!!若是有分庫分表分區的狀況,若是短期內插入的數據量太大,數據庫同步可能就會比較迷了,讀寫數據不一致的狀況在所不免了,可能會由於一張表的批量插入,影響整個DB服務組的同步,同時還要考慮併發問題,哈哈哈。 

 

四、Other

  能夠注意一下,我在上面寫的insert語句中,基本每一條命令都寫了插入的字段,以下:

insert into user (id, name, gender, addr, status) value (1, 'aaa', 1, 'beijing', 1);

  

  其實我知道表的各個字段的排列順序,徹底能夠省略字段名,以下:

insert into user value (1, 'aaa', 1, 'beijing', 1);

  

  這兩種方式的效率,這裏就不談了,不過第一種方式,在某些場景有優點,舉個例子:好比user表中增長create_time、update_time:

CREATE TABLE `user` (
  `id` 		int(11) 	NOT NULL AUTO_INCREMENT COMMENT '編號',
  `name` 	varchar(40) NOT NULL COMMENT '姓名',
  `gender` 	tinyint(1) 	DEFAULT '0' COMMENT '性別:1-男;2-女',
  `addr` 	varchar(40) NOT NULL COMMENT '住址',
  `status` 	tinyint(1) 	DEFAULT '1' COMMENT '是否有效',
  `create_time` timestamp  DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

  

  若是沒有強制要求create_time和update_time必須從客戶端接收,那麼徹底能夠用默認值,insert的時候不用下面的語句:

--- 強制create_time和update_time使用client傳遞值
insert into user 
	(id, name, gender, addr, status, create_time, update_time) 
value 
	(2, 'bbb', 0, 'shanghai', 1, '2019-11-09 18:00:00', '2019-11-09 18:00:00'),
	(3, 'ccc', 1, 'hangzhou', 0, '2019-11-09 18:00:00', '2019-11-09 18:00:00'),
	(4, 'ddd', 0, 'chongqing', 0, '2019-11-09 18:00:00', '2019-11-09 18:00:00');

--- create_time和update_time不須要強制使用client傳遞值,可使用默認值
insert into user 
	(id, name, gender, addr, status) 
value 
	(2, 'bbb', 0, 'shanghai', 1),
	(3, 'ccc', 1, 'hangzhou', 0),
	(4, 'ddd', 0, 'chongqing', 0);

  

  相似的,對於有些字段有默認值,而且批量插入的時候,都使用默認值時,能夠省略該字段,由於拼接sql的時候能夠少拼接一點,網絡傳輸的數據就少一點,能提高一點是一點吧,這個還得看實際狀況。

相關文章
相關標籤/搜索