一、背景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的时候能够少拼接一点,网络传输的数据就少一点,能提高一点是一点吧,这个还得看实际状况。