一、背景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
最初學習數據庫,都知道使用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一次插入一條數據的這種方式,絕大多數都是使用這種方式,來進行少許的數據插入!!!
若是用這種方式進行大量數據的入庫,哈哈,花的時間能夠喝好多杯咖啡了。
上面已經說到了,一次插入一條數據的主要缺陷是:須要創建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服務組的同步,同時還要考慮併發問題,哈哈哈。
能夠注意一下,我在上面寫的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的時候能夠少拼接一點,網絡傳輸的數據就少一點,能提高一點是一點吧,這個還得看實際狀況。