You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Dapper执行Oracle批量插入速度过慢的优化方案咨询

优化Oracle批量插入性能的几种实用方案

哇,200条数据插入耗时10秒确实有点拉胯,咱们来一步步把这个性能提上去!先拆解下你当前代码的核心问题:你用Dapper的ExecuteAsync传入集合时,默认是逐条执行每条插入语句——哪怕在事务里,每条都要走一次网络请求,200条就是200次往返,这才是慢的根源。下面给你几个针对性的优化方案,按性能提升幅度排序:

1. 用OracleBulkCopy(性能天花板)

Oracle官方提供的批量加载工具,直接绕过常规SQL解析,和数据库底层批量接口交互,是大数据量插入的最优解,200条数据用这个基本能降到毫秒级。

public async Task BulkInsertAsync()
{
    var items = GetItems().ToList();
    using var connection = OpenOracleConnection() as OracleConnection;
    using var transaction = connection.BeginTransaction();
    
    // 把数据转换成DataTable(OracleBulkCopy需要的格式)
    var dataTable = new DataTable();
    dataTable.Columns.Add("Column1", typeof(int));
    dataTable.Columns.Add("Column2", typeof(int));
    dataTable.Columns.Add("Column3", typeof(string));
    dataTable.Columns.Add("Column4", typeof(string));
    dataTable.Columns.Add("Column5", typeof(string));
    dataTable.Columns.Add("Column6", typeof(string));
    dataTable.Columns.Add("Column7", typeof(string));
    dataTable.Columns.Add("Column8", typeof(DateTime));
    dataTable.Columns.Add("Column9", typeof(string));
    dataTable.Columns.Add("Column10", typeof(int));
    
    foreach (var item in items)
    {
        dynamic dynItem = item;
        dataTable.Rows.Add(dynItem.p1, dynItem.p2, dynItem.p3, dynItem.p4, dynItem.p5, dynItem.p6, dynItem.p7, dynItem.p8, dynItem.p9, dynItem.p10);
    }
    
    // 执行批量插入
    using var bulkCopy = new OracleBulkCopy(connection, OracleBulkCopyOptions.UseInternalTransaction, transaction);
    bulkCopy.DestinationTableName = "TableName";
    // 显式映射列(列名一致时可省略,显式更稳妥)
    bulkCopy.ColumnMappings.Add("Column1", "Column1");
    bulkCopy.ColumnMappings.Add("Column2", "Column2");
    bulkCopy.ColumnMappings.Add("Column3", "Column3");
    bulkCopy.ColumnMappings.Add("Column4", "Column4");
    bulkCopy.ColumnMappings.Add("Column5", "Column5");
    bulkCopy.ColumnMappings.Add("Column6", "Column6");
    bulkCopy.ColumnMappings.Add("Column7", "Column7");
    bulkCopy.ColumnMappings.Add("Column8", "Column8");
    bulkCopy.ColumnMappings.Add("Column9", "Column9");
    bulkCopy.ColumnMappings.Add("Column10", "Column10");
    
    await bulkCopy.WriteToServerAsync(dataTable);
    transaction.Commit();
}

2. Dapper+Oracle FORALL(改动最小的优化)

如果不想换批量工具,用Oracle原生的FORALL语句结合Dapper的数组参数,把所有数据打包成数组一次性发送到数据库,减少网络往返次数。

public async Task InsertAsync()
{
    var items = GetItems().ToList();
    using var connection = OpenOracleConnection();
    using var transaction = connection.BeginTransaction();
    
    // 把每个字段提取成数组
    var p1 = items.Select(x => ((dynamic)x).p1).ToArray();
    var p2 = items.Select(x => ((dynamic)x).p2).ToArray();
    var p3 = items.Select(x => ((dynamic)x).p3).ToArray();
    var p4 = items.Select(x => ((dynamic)x).p4).ToArray();
    var p5 = items.Select(x => ((dynamic)x).p5).ToArray();
    var p6 = items.Select(x => ((dynamic)x).p6).ToArray();
    var p7 = items.Select(x => ((dynamic)x).p7).ToArray();
    var p8 = items.Select(x => ((dynamic)x).p8).ToArray();
    var p9 = items.Select(x => ((dynamic)x).p9).ToArray();
    var p10 = items.Select(x => ((dynamic)x).p10).ToArray();

    // 用Oracle FORALL批量执行插入
    await connection.ExecuteAsync(@"
        FORALL i IN 1..:p1.Count
            INSERT INTO TableName (Column1, Column2, Column3, Column4, Column5, Column6, Column7, Column8, Column9, Column10)
            VALUES (:p1(i), :p2(i), :p3(i), :p4(i), :p5(i), :p6(i), :p7(i), :p8(i), :p9(i), :p10(i))
    ", new { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 }, transaction: transaction);
    
    transaction.Commit();
}

3. 额外的小优化(锦上添花)

  • 修复Random的小问题:你当前GetItems里每次循环都new一个Random,会因为系统时钟种子重复导致随机数重复,改成只实例化一次:
    private static IEnumerable<object> GetItems() {
        var list = new List<object>();
        var random = new Random(); // 只创建一次
        for (int i = 0; i < 200; i++) {
            list.Add(new { p1 = random.Next(0,999999), p2 = 1, p3 = "-", p4 = "--", p5 = "--", p6 = "---", p7 = "----", p8 = DateTime.Now, p9 = "-", p10 = 1 });
        }
        return list;
    }
    
  • 临时禁用索引/约束:如果表上有大量索引或外键,插入时可以临时禁用,插入完成后再重建(仅适合一次性批量插入,并发场景别用):
    -- 禁用索引
    ALTER INDEX idx_tablename_column1 DISABLE;
    -- 插入完成后重建
    ALTER INDEX idx_tablename_column1 REBUILD;
    
  • 优化连接池:确保连接字符串开启连接池(默认Pooling=true),可以设置Min Pool Size避免频繁创建销毁连接的开销。

内容的提问来源于stack exchange,提问作者Fatih Yıldırım

火山引擎 最新活动