使用EntityManager向MySQL插入10万条数据过慢的问题咨询
嘿,我瞅了你这段批量插入MySQL的代码,这么干肯定要踩坑啊——10万条数据还每条带1万个坐标,直接循环persist的话,EntityManager的*一级缓存(Persistence Context)*会把所有实体都攒着,最后提交的时候内存大概率炸锅,而且插入速度慢到你怀疑人生。给你几个实打实的优化方案,亲测有效:
1. 分批提交+清理一级缓存
EntityManager的一级缓存会保存所有persist过的实体,10万条量级的话内存绝对扛不住。所以要每插一批就提交一次,并且主动清空缓存释放内存:
EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); long startTime = System.nanoTime(); int batchSize = 500; // 可根据服务器内存调整,一般500-1000合适 try { for (int y = 0; y < 100000; y++) { RealVector real = new RealVector(10000); for (int i = 0; i < 10000; i++) { real.getCoordinates().add((float) i); } em.persist(real); // 每达到批次量就提交并清理缓存 if (y % batchSize == 0 && y != 0) { transaction.begin(); transaction.commit(); em.clear(); // 清空一级缓存,释放内存 transaction = em.getTransaction(); // 重新获取事务对象 } } // 提交最后一批不足批次量的数据 if (100000 % batchSize != 0) { transaction.begin(); transaction.commit(); em.clear(); } } finally { em.close(); // 别忘了关闭EntityManager } long endTime = System.nanoTime(); System.out.println("总耗时: " + (endTime - startTime) / 1e9 + " 秒");
2. 开启JPA批量插入配置
默认情况下,JPA可能还是会生成单条插入的SQL,要让它真正生成批量插入语句,得在persistence.xml里加配置(以Hibernate为例):
<property name="hibernate.jdbc.batch_size" value="500"/> <!-- 和代码里的批次量对应 --> <property name="hibernate.order_inserts" value="true"/> <!-- 按实体类型排序插入,提升批量效率 --> <property name="hibernate.order_updates" value="true"/> <property name="hibernate.jdbc.batch_versioned_data" value="true"/> <!-- 支持版本化数据的批量操作 -->
这些配置能让Hibernate把同类型的插入SQL攒成批量语句,大幅减少和数据库的交互次数,速度直接起飞。
3. 优化坐标字段的存储方式(重中之重!)
你现在用List<Float>存10000个坐标,默认会被映射成关联表或者超大的文本字段,这会极大拖慢插入速度。推荐两种优化方式:
- 序列化二进制存储:把List序列化成字节数组,存成MySQL的BLOB类型,每条数据只占一个字段,插入和查询都快
- JSON类型存储:如果需要偶尔查询单个坐标,可存成MySQL的JSON类型,但序列化二进制的性能更优
修改实体类的坐标字段示例:
@Column(columnDefinition = "BLOB") private byte[] coordinates; // 把List<Float>转成字节数组存库 public void setCoordinates(List<Float> coords) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(coords); this.coordinates = bos.toByteArray(); } catch (IOException e) { throw new RuntimeException("序列化坐标失败", e); } } // 从字节数组反序列化成List<Float> public List<Float> getCoordinates() { try (ByteArrayInputStream bis = new ByteArrayInputStream(coordinates); ObjectInputStream ois = new ObjectInputStream(bis)) { return (List<Float>) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("反序列化坐标失败", e); } }
这样修改后,每条数据的存储体积会大幅缩小,插入效率直接提升一个量级。
4. 替换主键生成策略(如果允许)
你用了@TableGenerator生成主键,这种方式每次插入都会去查询主键表拿新ID,10万条就是10万次额外查询,巨慢。如果业务允许,改成MySQL的自增主键:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
让数据库自动生成主键,省去额外的主键查询操作,性能提升明显。
内容的提问来源于stack exchange,提问作者p192
相关产品推荐
相关产品推荐

