大量List数据批量插入数据库很高效的方法(实践结果)

下面方法适用于大量(百万级别的List数据),插入到Oracle表中

private static final int OPENID_PHONE_NUM = 800; //经实践,800一批插入相对较快,这个可以随便定义

private void insertPMeSendToPhoneInfo(List<PMeSendToPhone> phoneList){

    int listSize=phoneList.size();
    int toIndex=OPENID_PHONE_NUM;
    for(int i = 0;i<phoneList.size();i+=OPENID_PHONE_NUM){
        if(i+OPENID_PHONE_NUM>listSize){//作用为toIndex最后没有800条数据则剩余几条newList中就装几条
            toIndex=listSize-i;
        }
        List<PMeSendToPhone> newPMeSendToPhoneInfo = phoneList.subList(i,i+toIndex);
        //批量插入
        pMeSendToPhoneMapper.batchInsertPMePhoneData(newPMeSendToPhoneInfo);
    }
}

 

/**  接口方法定义
 *批量插入
 * @param newPMeSendToPhoneInfo
 * @return
 */
int batchInsertPMePhoneData(@Param(value = "list") List<PMeSendToPhone> newPMeSendToPhoneInfo);

mybits配置文件定义方法

<insert id="batchInsertPMePhoneData" parameterType="java.util.List">
  begin
  <foreach collection="list" item="item" index="index">
    insert into P_ME_SEND_TO_PHONE (PHONEID, PHONE, OPENID,  EXTEND_1,EXTEND_2, EXTEND_3)
    values(
    SEQ_P_ME_SEND_TO_PHONE.nextval,
    #{item.phone,jdbcType=VARCHAR},
    #{item.openid,jdbcType=VARCHAR},
    #{item.extend1,jdbcType=VARCHAR},
    #{item.extend2,jdbcType=VARCHAR},
    #{item.extend3,jdbcType=VARCHAR}
    );
  </foreach>
  end;
</insert>

 

 

 

 

<think>嗯,用户问的是向数据库插入大量数据会导致什么问题以及如何优化。这个问题看起来挺常见的,尤其是在处理数据或者高并发场景的时候。我需要先理清楚可能的问题点,然后逐一分析对应的优化方法。 首先,插入大量数据可能会遇到性能问题。比如,每次插入都开启一个事务,频繁提交的话,I/O压力会很,尤其是机械硬盘,频繁写操作可能导致磁盘瓶颈。另外,如果每次插入都单独执行,网络延迟和SQL解析的开销也会累积起来,影响整体速度。 然后是锁的问题。比如在MySQL的InnoDB引擎中,行锁可能会升级为锁,特别是在没有使用合适的索引或者批量插入的时候,导致其他操作被阻塞,并发性能下降。 还有索引维护的成本。每插入一条数据数据库需要更新所有相关的索引,如果数据量,索引的维护会变得非常耗时,尤其是在插入完成后才创建索引的情况下,可能会占用大量资源。 日志写入也是一个问题。比如,事务日志(如InnoDB的redo log)和二进制日志(binlog)在每次插入时都需要写入,大量数据插入会导致日志文件迅速增长,写日志成为性能瓶颈。 自增主键的竞争也不容忽视。在高并发插入的情况下,自增主键的争用可能导致锁竞争,特别是在分库分或者分布式系统中,自增ID的生成可能成为瓶颈。 接下来是优化方法。首先想到的是批量插入,用INSERT INTO ... VALUES (...), (...), ...的方式减少事务和网络开销。不过需要注意SQL语句的长度限制,可能需要分批次处理。 事务的优化,把多个插入操作合并到一个事务中,减少提交次数,降低I/O压力。不过要注意事务不能太,否则回滚日志会占用太多内存。 禁用索引和约束检查,比如在插入前先暂时去掉索引,插入后再重建,这样减少每次插入的索引维护开销。但需要确保数据的一致性,插入期间相关查询可能受影响。 使用LOAD DATA INFILE或者类似工具,直接从文件导入数据,比逐条插入快很多,因为绕过了SQL解析和事务开销。 调整日志策略,比如在批量插入时暂时关闭二进制日志,或者增日志文件小,减少刷盘次数,但要注意数据安全,确保有备份和恢复机制。 分区或分库分,把数据分散到不同的物理存储中,提高并行插入能力。不过需要设计合理的分区键,避免数据倾斜。 应用层缓冲队列,先缓存数据到内存或消息队列,再批量写入数据库,削峰填谷,避免瞬时压力过。但需要考虑数据丢失的风险,可能需要持久化机制。 还有自增主键的优化,使用更高效的ID生成算法,比如雪花算法,避免单点竞争。或者调整自增步长,减少锁冲突。 不过可能还要考虑具体数据库的类型和配置,比如MySQL、PostgreSQL的不同优化参数,存储引擎的选择,硬件方面的优化,比如SSD、RAID配置,或者增加内存减少磁盘I/O。 另外,异步插入也是一个方向,比如用后台任务或者队列处理插入操作,不阻塞主业务流程。但要评估数据一致性的要求,是否允许延迟写入。 总的来说,需要根据具体的业务场景、数据库类型、硬件配置等因素综合考虑,选择合适的优化组合。同时,测试和监控是关键,优化后要实际验证效果,观察系统资源的使用情况,比如CPU、内存、磁盘I/O、网络等指标,确保优化措施有效且没有引入新的问题。</think>向数据库插入大量数据可能导致以下问题及优化方案: ### 一、常见问题 1. **性能瓶颈** - I/O压力:频繁磁盘写入导致硬盘成为瓶颈(机械硬盘尤为明显) - 网络延迟:逐条插入时网络往返时间累积 - SQL解析开销:每条INSERT语句都需要解析执行计划 2. **锁竞争** - 行锁升级为锁(如MySQL InnoDB的锁升级机制) - 自增主键争用导致插入间隙锁竞争 3. **索引维护** - B+树索引的页分裂概率增加(特别是随机插入场景) - 唯一性校验带来的额外开销 4. **日志压力** - 事务日志(redo log)频繁刷盘 - 二进制日志(binlog)同步延迟 5. **事务管理** - 自动提交模式下的频繁事务提交 - 事务导致的undo log膨胀 ### 二、优化方案 #### 1. 批量操作(核心方案) ```sql -- 标准批量插入 INSERT INTO table (col1,col2) VALUES (v1,v2),(v3,v4)...(vn,vn); -- MySQL特有语法(可提升30%-50%性能) INSERT DELAYED INTO ... ``` #### 2. 事务优化 ```sql START TRANSACTION; -- 执行批量插入 COMMIT; ``` - 推荐事务小:1MB~10MB(根据redo log配置调整) - 注意innodb_log_buffer_size参数设置 #### 3. 索引优化 ```sql -- 插入前禁用索引 ALTER TABLE table DISABLE KEYS; -- 插入完成后重建索引 ALTER TABLE table ENABLE KEYS; ALTER TABLE table REBUILD PARTITION ALL; ``` #### 4. 并行写入 ```python # Python多线程示例(需配合连接池) from concurrent.futures import ThreadPoolExecutor def batch_insert(data_chunk): with connection.cursor() as cursor: cursor.executemany(sql, data_chunk) with ThreadPoolExecutor(max_workers=8) as executor: executor.map(batch_insert, chunked_data) ``` #### 5. 文件加载(极速方案) ```sql -- MySQL文件加载(速度是INSERT的10-20倍) LOAD DATA INFILE '/path/data.csv' INTO TABLE table FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'; ``` #### 6. 架构优化 - **分区**:按时间或哈希分区 ```sql CREATE TABLE sales ( id INT NOT NULL, sale_date DATE NOT NULL ) PARTITION BY RANGE(YEAR(sale_date)) ( PARTITION p0 VALUES LESS THAN (2020), PARTITION p1 VALUES LESS THAN (2021) ); ``` - **读写分离**:主库写从库读 - **分库分**:使用ShardingSphere等中间件 #### 7. 参数调优(MySQL示例) ```ini # my.cnf优化项 bulk_insert_buffer_size=256M innodb_autoinc_lock_mode=2 innodb_flush_log_at_trx_commit=0 # 批量插入时临时关闭 sync_binlog=0 ``` #### 8. 异步写入 ```java // Java队列异步处理示例 BlockingQueue<Data> queue = new LinkedBlockingQueue(10000); // 生产者线程 public void produce(Data data) { queue.put(data); } // 消费者线程 public void consume() { List<Data> buffer = new ArrayList<>(1000); while(true) { Data data = queue.poll(100, TimeUnit.MILLISECONDS); if(data != null) buffer.add(data); if(buffer.size() >= 1000 || (buffer.size()>0 && data==null)) { batchInsert(buffer); buffer.clear(); } } } ``` ### 三、特殊场景处理 1. **时序数据**:使用TimescaleDB等时序数据库 2. **日志类数据**:采用LSM-Tree结构的存储引擎 3. **实时分析**:配合Kafka+流处理中间件 ### 四、监控指标 1. 磁盘IOPS(建议SSD不低于5000 IOPS) 2. 每秒插入行数(Rows/sec) 3. InnoDB行操作统计 ```sql SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted'; ``` 4. 锁等待时间 ```sql SHOW STATUS LIKE 'innodb_row_lock%'; ``` ### 五、压测建议 使用sysbench工具测试: ```bash sysbench oltp_insert \ --table-size=1000000 \ --tables=10 \ --mysql-host=127.0.0.1 \ --mysql-user=test \ --mysql-password=test \ prepare ``` > **最佳实践**:某电商平台通过"LOAD DATA+分区+异步队列"组合方案,将每日1亿条订单明细的插入耗时从6小时缩短至23分钟。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值