以 Mysql 为例
插入大量数据导致越来越慢甚至崩溃
越来越慢 说明执行当前的操作可能已经占用了你大量的内存,数据库本身执行操作越来越费力,电脑是在被搞得太忙了处理的事情太多,几乎处理不过来了,这个时候显然如果能释放不需要的内存资源,或者提高数据库本身处理数据的性能自然是最有效的提升方式。
大批量的数据操作
一方面 是我们的代码对数据库数据的操作逻辑有直接的影响,因为我们直接决定了数据库操作数据的方式。
一方面 是我们环境本身硬件设备的投入和建设力度。
还有一方面 数据库为了应对不同的场景时需要修改本身的一些配置来适应我们的项目。
可能也会涉及网络稳定性以及我们项目架构等方面这里不多说。我们直接说说直观的修改方案以及操作,见下文说明。
最为普遍可见的,即 for 循环一条一条执行,实在是没什么说的直接跳过。
拼接多个Values,即书写多条写入数据时如下格式操作:
INSERT INTO `table_name1` (字段1, 字段2,字段3,.......) VALUES (值1,值2,值3…),(值1,值2,值3…….)
,(值1,值2,值3…………);
一条 INSERT 语句写入多条值,这种方式对付一般批量写入是没有多大问题的至少可以应付一万左右的数据量,当然每一条INSERT 后面拼接多少条 Values 还是需要自行去实践以便得出最适合自己系统负荷的情况。
拼接values时可以使用stringBuffer(线程安全但较慢),或者Stringbuilder(速度更快但线程不安全)来拼接。
这样写的好处其实就是减少对 SQL 的解析过程和多次连接过程,从而带来性能上的提升和节约。
修改存储过程,也就是 MySQL AutoCommit 的配置值(也可以理解为一个任务集合,当任务集达到一定数量后我们再去提交任务和执行),默认情况下Autocommit 是开启的,效果就是我们每次执行数据库操作,这个动作都会立马执行也就是commit到数据库,从而发生数据变化得到我们想要的结果。
但当我们关闭 AutoCommit 也就是 AutoCommit=0 时,我们的对数据库的操作需要我们手动去提交,否则将会回滚也就是我们的操作最终将不生效。
通过使用手动 Commit 的方式,我们可以将我们将要操作的 INSERT 等任务先囤积下来,然后设定一个阈值,当数量达到这个阈值后,我们执行手动提交,让数据库一次性去处理这些任务,这里给出关键代码伪代码,
已JDBC为例:
connection.setAutoCommit(false);
// 设定一个阈值为 200
// 通过 for 或者其他循环开始囤积任务
for(){
...........
...addBatch();
...executeBatch();
// 当达到阈值后提交
connection.commit();
}
// 完成本次批量后关闭相关资源
.....close();
通过调整存储过程的配置,手动提交可以较大幅度的降低与数据库之间发生的对话,节省建立连接的资源和时间。
但切记,如果没有自动释放资源的框架或者操作时,最后一定一定要执行 close() 操作释放资源,否则你的内存占用将持续升高,最终速度和性能还是最跌落下来。
这样的操作对付几万数据问题不是太大
即在语句内使用拼接的同时,执行手动 commit 事务,设置AutoCommit 为关闭状态,这样的话语句拼接的阈值和 commit 的阈值自己就要多尝试一下了,但这样带来的提升绝对是翻倍的效果!
同样的最后一定一定要执行 close() 操作释放资源,否则你的内存占用将持续升高,最终速度和性能还是最跌落下来。
也就是初学JDBC时我们一直要求的关闭资源,我们从较难的问题回到了我们的基础上。及时释放资源可以防止内存占用过高,防止速度骤降,避免影响系统响应速度。
从功能上来说就是使用读写文件的方式写入数据到数据库,MySQL文档上说要快20倍左右。
但有缺点,导入数据之前,必须要有文件,也就是说从文件中导入数据,这就需要去写文件,以及文件删除等维护操作,某些情况下,比如数据源并发的话,还会出现写文件并发问题,很难处理,但Mysql社区提供了从内存中读写的方式,即
setLocalInfileInputStream() 方法。
此方法位于com.mysql.jdbc.PreparedStatement 类中
通过使用 MySQL JDBC 的setLocalInfileInputStream 方法实现从Java InputStream中load data local infile 到MySQL数据库中
通过 LOAD DATA LOCAL INFILE 这种方式写入数据时,数据量越大,字段越多效果越明显!
主键:表建立主键时, mysql会对主键建立索引,默认使用Btree索引,每次加入新的数据都会导致Btree索引更新,而且无法删除。主键的存在导致主键约束即唯一性要不断的检测随着数据量的上升将带来一定的消耗。
主键可以使用自增减少消耗,但有时候为了业务独立性并不建议这样做。
索引:与上面同理,一般的索引建立我们可以放到所有数据导入后再建立,可以减少一定的消耗。
innodb用b+树存储数据,表中数据越多,树的层数越多,所以越慢。
插入的方式使用按照自增 ID 的顺序插入,还是随机插入。
插入顺序的不同意味着 B+ 树随着数量的增大,要不断进行页分裂的情况,造成性能降低。
InooDB 为了保证更新的效率,采用了 WAL 的机制,先写内存,后写磁盘。redo-log 的大小配置的是否合适,当 write pos 追上 check point 时,表示 redolog 快使用完了,所有更新操作都会被暂停,然后进行 flush 操作,将内存的数据写入到磁盘上。
刷脏页的策略是否正常,对应 innodb_io_capacity 这个参数,如果 TPS 写入很低,但磁盘 IO 不高,很可能就是这个参数设置的不正确。在插入时,可以关注下脏页比例,默认从 75% 开始进行 flush 操作,将数据刷到磁盘。
使用默认MySQL配置,默认innodb,十几万左右的数据量是没有太大问题,优先考虑代码的优化,其次操作数据库配置。