用JDBC向数据库中插入大量数据时的优化

JDBC批量插入性能测试
本文对比了使用Statement、PreparedStatement及PreparedStatement+批处理三种方式通过JDBC向数据库插入10万条记录的性能。测试结果显示,PreparedStatement+批处理的方式性能最优,特别是当每1000条记录进行一次批处理提交时,总耗时仅1秒。

使用jdbc向数据库插入100000条记录,分别使用statement,PreparedStatement,及PreparedStatement+批处理3种方式进行测试: 

//1.使用statement插入100000条记录 

public void exec(Connection conn){
  try {
   //开始时间
   Long beginTime = System.currentTimeMillis();
   //设置手动提交
   conn.setAutoCommit(false);
    
   Statement st = conn.createStatement();

   for(int i=0;i<100000;i++){
      String sql="insert into t1(id) values ("+i+")";
      st.executeUpdate(sql);  
   }
   
   //结束时间
   Long endTime = System.currentTimeMillis();

   System.out.println("st:"+(endTime-beginTime)/1000+"秒");//计算时间

   st.close();
   conn.close();
  } catch (SQLException e) {
   e.printStackTrace();
  }  
} 


//2.使用PreparedStatement对象 

public void exec2(Connection conn){
  try {
   Long beginTime = System.currentTimeMillis();
   conn.setAutoCommit(false);//手动提交
   PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");
   for(int i=0;i<100000;i++){
    pst.setInt(1, i);
    pst.execute();    
   }
   conn.commit();
   Long endTime = System.currentTimeMillis();
   System.out.println("pst:"+(endTime-beginTime)/1000+"秒");//计算时间
   pst.close();
   conn.close();
  } catch (SQLException e) {
   e.printStackTrace();
  }

} 

//3.使用PreparedStatement + 批处理 
public void exec3(Connection conn){
  try {
   conn.setAutoCommit(false);
   Long beginTime = System.currentTimeMillis();
   //构造预处理statement
   PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");
   //1万次循环
   for(int i=1;i<=100000;i++){    
    pst.setInt(1, i);
    pst.addBatch();
    //每1000次提交一次
    if(i%1000==0){//可以设置不同的大小;如50,100,500,1000等等
     pst.executeBatch();
     conn.commit();
     pst.clearBatch();
    }
   }
   Long endTime = System.currentTimeMillis();
   System.out.println("pst+batch:"+(endTime-beginTime)/1000+"秒");
   pst.close();
   conn.close();
  } catch (SQLException e) {
   e.printStackTrace();
  }
}

在Oracle 10g中测试,结果: 
1.使用statement  耗时142秒; 
2.使用PreparedStatement 耗时56秒; 
3.使用PreparedStatement + 批处理耗时: 
   a.50条插入一次,耗时5秒; 
   b.100条插入一次,耗时2秒; 
   c.1000条以上插入一次,耗时1秒; 
通过以上可以得出结论,在使用jdbc大批量插入数据时,明显使用第三种方式(PreparedStatement + 批处理)性能更优。

<think>我们正在讨论数据库批量插入数据使用自增ID的实现方案。根据引用内容,我们可以总结出两种主要方案:数据库层自增ID和代码层生成ID。1.**数据库层自增ID**:在创建表,将主键字段设置为自增(如MySQL的`auto_increment`),这样在插入数据数据库会自动为每条记录生成唯一的自增ID。批量插入,我们不需要在代码中指定ID值,数据库会自动处理。2.**代码层生成ID**:如果表的主键不是自增的,那么需要在插入数据之前,在代码中为每条记录生成唯一的ID。这可以通过不同的方式实现,例如使用UUID、雪花算法(Snowflake)生成分布式ID,或者通过一个ID管理服务来获取一批ID(如引用[4]所示)。现在,根据用户需求,他们想寻找在批量插入大量数据使用自增ID的可行方案。这里我们主要关注数据库自增ID的方案,因为这是最直接的方式。###数据库自增ID的批量插入实现####1.MySQL数据库在MySQL中,我们可以使用`auto_increment`关键字定义自增主键。例如:```sqlCREATETABLEtest(idINTNOTNULLAUTO_INCREMENT,statusINTNOTNULLDEFAULT-1,desVARCHAR(45)DEFAULTNULL,PRIMARYKEY(id));```在批量插入数据,我们不需要指定`id`列的值,数据库会自动分配自增ID。例如,插入多条数据:```sqlINSERTINTOtest(status,des)VALUES(3,'desss'),(3,'another'),(4,'more');```####2.在编程语言中实现批量插入在应用程序中,我们可以使用不同的方式执行批量插入。**使用MyBatis(Java)**:在MyBatis中,我们可以使用`@Insert`注解或XML映射文件来执行批量插入。对于自增ID,我们可以在插入后获取生成的主键。例如,在Mapper接口中定义方法:```java@Insert({"<script>","INSERTINTOtest(status,des)VALUES","<foreachcollection='list'item='item'separator=','>","(#{item.status},#{item.des})","</foreach>","</script>"})@Options(useGeneratedKeys=true,keyProperty="id")voidbatchInsert(List<Test>list);```这里`useGeneratedKeys=true`表示使用数据库生成的主键,`keyProperty`指定了将生成的主键值赋给实体对象的哪个属性。这样,在插入完成后,每个Test对象的id属性会被自动设置。**注意**:在批量插入,MyBatis需要数据库驱动支持返回多个自增ID。MySQL的JDBC驱动支持这个功能(需要设置`allowMultiQueries=true`?但通常对于单条INSERT多值语句是支持的)。另外,也可以考虑分批次插入以避免一次性插入过多数据。####3.存储过程批量插入如引用[2]所示,可以使用存储过程来批量生成数据:```sqlDELIMITER$CREATEPROCEDUREinitData()BEGINDECLAREiINTDEFAULT0;WHILEi<=10000DOINSERTINTOtest(status,des)value(3,"desss");SETi=i+1;ENDWHILE;END$DELIMITER;CALLinitData();```这种方式直接在数据库端执行,不经过应用程序,适合初始化大量测试数据。###非自增ID的批量插入方案如果由于某些原因不能使用数据库自增ID(例如分库分表场景),则需要在代码中生成ID。####1.使用ID管理服务如引用[4]所示,可以有一个ID管理服务,每次获取一批ID:```java//获取一个IDintid=idManagementService.getId("table_name");//或者获取多个IDintstartId=idManagementService.getId("table_name",count);```然后为每个要插入的实体设置ID。####2.使用分布式ID生成算法例如使用雪花算法生成ID,这样每个实体在创建就可以设置一个唯一的ID,然后批量插入。###性能考虑-**数据库自增ID**:在批量插入数据库需要为每行生成一个自增ID,这通常是高效的。但要注意,自增ID在分布式数据库环境中可能会成为瓶颈(需要全局唯一的自增ID),或者在高并发插入可能产生锁竞争。-**代码生成ID**:可以避免数据库的竞争,但需要保证生成的ID全局唯一,且有序性可能不如自增ID。###总结对于批量插入大量数据并希望使用自增ID的场景,推荐使用数据库的自增ID特性。具体步骤:1.将表的主键设置为自增(如MySQL的`AUTO_INCREMENT`)。2.在应用程序中执行批量插入操作,不指定主键ID的值。3.如果需要获取插入后生成的自增ID,在MyBatis等ORM框架中配置`useGeneratedKeys`。如果是在分布式系统中,需要考虑全局唯一ID,则可以使用代码层生成ID的方案,如雪花算法或ID服务。§§相关问题§§1.在分布式数据库中如何实现全局唯一的自增ID?2.批量插入数据,如何优化插入性能?3.使用MyBatis批量插入后,如何获取每个插入记录的自增ID?4.数据库自增ID和UUID作为主键的优缺点比较?</think>### 数据库批量插入数据自增ID实现方案 在数据库批量插入中实现自增ID主要有两种方案:**数据库层自增**和**应用层预生成**。以下是详细实现方案: --- #### 方案一:数据库层自增 (推荐) **核心原理**:依赖数据库的`AUTO_INCREMENT`特性自动生成ID **适用场景**:MySQL、PostgreSQL等支持自增主键的数据库 **实现步骤**: 1. **建表定义自增主键** ```sql CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT, -- 关键定义 name VARCHAR(50), age INT, PRIMARY KEY (id) ); ``` 2. **批量插入不指定ID** ```sql INSERT INTO users (name, age) VALUES ('张三', 25), ('李四', 30), ('王五', 28); ``` 3. **获取自增ID的方法**(以MyBatis为例): ```xml <insert id="batchInsert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (name, age) VALUES <foreach item="item" collection="list" separator=","> (#{item.name}, #{item.age}) </foreach> </insert> ``` ```java // Java实体类会自动填充ID userList.forEach(user -> System.out.println(user.getId())); ``` **优势**: - ⚡ 高性能:数据库原生优化批量插入 - 🔒 原子性:避免ID冲突风险 - 📦 简单:无需额外ID生成逻辑[^2][^5] --- #### 方案二:应用层预生成ID **核心原理**:在代码中提前生成ID再批量插入 **适用场景**:分库分表、分布式系统、非自增主键表 **实现步骤**: 1. **通过ID服务获取ID区间** ```java // 获取批量ID(示例伪代码) int startId = idService.getNextIdBatch("users", 100); ``` 2. **实体类填充ID** ```java List<User> users = new ArrayList<>(); for (int i = 0; i < 100; i++) { User user = new User(); user.setId(startId + i); // 应用层分配ID user.setName("name_" + i); users.add(user); } ``` 3. **执行批量插入** ```sql INSERT INTO users (id, name, age) VALUES (1001, '张三', 25), (1002, '李四', 30), (1003, '王五', 28); ``` **关键实现**(引用[4]案例): ```java // 从ID服务获取ID块 int idBlock = idManagementService.getId("user_table", batchSize); // 为每个实体分配ID for (User user : userList) { user.setId(idBlock++); } ``` **优势**: - 🌐 分布式友好 - 🔗 支持非自增主键表 - ⏱️ 插入前即可获得ID[^1][^4] --- ### 性能优化建议 1. **批量大小控制** - MySQL建议每批500-1000条(超过可能锁表) 2. **事务提交** ```sql START TRANSACTION; -- 批量插入语句 COMMIT; ``` 3. **禁用索引**(超大数据量) ```sql ALTER TABLE users DISABLE KEYS; -- 执行插入 ALTER TABLE users ENABLE KEYS; ``` > 💡 **方案选择建议**: > - 单数据库优先选**数据库自增**(简单高效) > - 分布式系统选**应用层生成**(如雪花算法)[^3][^4] ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值