You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin批量插入时触发阻塞式GC Explicit问题求助

问题分析与解决方案

我来帮你分析下这个问题——从GC日志和代码来看,核心问题是批量插入过程中内存占用过高,导致频繁GC最终卡住。咱们一步步拆解问题,再给出针对性的优化方案:

从GC日志看本质

11-19 11:52:17.463 D/Mono (32524): GC_MAJOR: (LOS overflow) time 50.76ms, stw 60.27ms los size: 4096K in use: 1369K
11-19 11:52:17.693 D/Mono (32524): GC_MINOR: (Nursery full) time 2.45ms, stw 3.05ms promoted 10K major size: 8240K in use: 6634K

这些日志说明:

  • 小对象堆(Nursery)频繁被占满:意味着大量临时小对象(比如JSON序列化产生的中间对象、字符串片段)没有及时回收
  • 大对象堆(LOS)溢出:你的代码中生成了超大的SQL字符串(1000条INSERT拼接在一起),直接撑爆了大对象堆,触发阻塞式GC,最终导致程序卡住

现有代码的核心问题

  1. 超大SQL字符串拼接:第一个BulkInsert方法把1000条INSERT语句拼成一个巨型字符串,拼接过程中还会产生多个中间字符串,内存占用呈指数级增长
  2. 冗余的JSON序列化:GenerateInsertStatement里把JToken转字符串再反序列化为Dictionary,平白产生大量临时对象
  3. 未优化的事务与Command复用:第二个BulkInsert虽然用了事务,但循环中每次创建新的SQLiteCommand,既浪费资源又增加内存开销
  4. 内存未及时释放:分页循环中,JArray等大对象在使用后没有主动释放,内存累积到最后一页彻底耗尽

针对性优化方案

1. 改用参数化批量插入+事务(核心优化)

优先推荐用SQLite-net ORM的批量插入API,它内部做了性能优化;如果必须用原生SQL,就用参数化查询避免超大字符串拼接:

方案一:SQLite-net ORM批量插入(最简洁高效)

直接把JArray反序列化为实体列表,用InsertAll批量插入:

public void BulkInsert(JArray array, string tableName = "") {
    try {
        // 直接反序列化为实体,跳过冗余的Dictionary转换
        var ordensList = array.ToObject<List<OrdemServico>>();
        using (var connection = new SQLiteConnection(DataBaseUtil.GetDataBasePath())) {
            var transaction = connection.BeginTransaction();
            try {
                connection.InsertAll(ordensList);
                transaction.Commit();
            } catch {
                transaction.Rollback();
                throw;
            }
        }
    } catch (Exception e) {
        LogUtil.WriteLog(e);
    }
}

方案二:原生SQL参数化批量插入

如果必须用原生SQL,复用SQLiteCommand并使用参数化查询,避免字符串拼接:

public void BulkInsert(JArray array, string tableName = "") {
    try {
        if (string.IsNullOrEmpty(tableName)) {
            tableName = typeof(OrdemServico).Name;
        }
        using (var connection = new SQLiteConnection(DataBaseUtil.GetDataBasePath())) {
            var transaction = connection.BeginTransaction();
            try {
                // 生成带参数占位符的INSERT模板
                var firstOrdem = array[0].ToObject<OrdemServico>();
                var properties = typeof(OrdemServico).GetProperties()
                    .Where(p => p.Name != "Id"); // 排除主键字段
                var columns = string.Join(",", properties.Select(p => p.Name));
                var placeholders = string.Join(",", properties.Select((p, idx) => $"@param{idx}"));
                var sql = $"INSERT INTO {tableName} ({columns}) VALUES ({placeholders});";

                using (var command = connection.CreateCommand(sql)) {
                    foreach (var item in array) {
                        var ordem = item.ToObject<OrdemServico>();
                        // 绑定参数
                        for (int i = 0; i < properties.Count(); i++) {
                            var prop = properties.ElementAt(i);
                            command.Parameters.AddWithValue($"@param{i}", prop.GetValue(ordem));
                        }
                        command.ExecuteNonQuery();
                        command.Parameters.Clear(); // 复用Command,清空参数
                    }
                }
                transaction.Commit();
            } catch {
                transaction.Rollback();
                throw;
            }
        }
    } catch (Exception e) {
        LogUtil.WriteLog(e);
    }
}

2. 优化JSON处理,减少临时对象

修改HTTP请求方法,直接返回JArray而非Object,避免后续类型转换的开销:

protected async Task<JArray> GetJArray(string metodo = "", string parametros = "") {
    try {
        var urlBase = new Uri(GetStringConnectionParameters(metodo) + parametros);
        using (HttpClient client = new HttpClient())
        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, urlBase))
        using (HttpResponseMessage response = await client.SendAsync(request)) {
            if (response.IsSuccessStatusCode) {
                var content = await response.Content.ReadAsStringAsync();
                return JArray.Parse(content); // 直接返回JArray,跳过不必要的Object转换
            }
        }
    } catch (Exception e) {
        LogUtil.WriteLog(e);
    }
    return null;
}

3. 及时释放内存,缓解GC压力

在分页循环中,处理完当前页后主动释放大对象引用,并按需触发GC:

private async Task GenerateOrdensServico() {
    try {
        _logs.Add("ORDENS DE SERVIÇO");
        double increment = ((1 - Progresso) / 2);
        int records = await _ordemServicoRest.GetCount();
        int limit = _sistemaParametroRepository.GetTamanhoPaginaSincMobile();
        int pages = (records / limit);
        for (int i = 0; i <= pages; i++) {
            JArray ordensServico = await _ordemServicoRest.GetAllInJsonFormatPaginated(DataBaseUtil.GetPagination(i, limit));
            if (ordensServico == null) {
                _logs.Add("Não Contem O.S de Corte para importar!");
                await App.Current.MainPage.DisplayAlert("Atenção", "Não tem O.S para importar!", "OK");
                continue;
            }
            _ordemServicoRepository.BulkInsert(ordensServico);
            
            // 主动释放大对象引用,让GC可以回收内存
            ordensServico = null;
            // 仅在内存压力大时触发GC,避免频繁调用影响性能
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
            GC.WaitForPendingFinalizers();
            
            AlterProgress(Progresso += ((Progresso * 100) + (increment / pages)));
        }
    } catch (Exception e) {
        LogUtil.WriteLog(e);
    }
}

4. 完善事务回滚逻辑

确保异常时事务能正确回滚,避免数据库数据不一致:

// 针对你之前的第二个BulkInsert方法补充回滚逻辑
public void BulkInsert(JArray array, string tableName = "") {
    SQLiteTransaction transaction = null;
    try {
        if (string.IsNullOrEmpty(tableName)) {
            tableName = typeof(T).Name;
        }
        using (SQLiteConnection connection = new SQLiteConnection(DataBaseUtil.GetDataBasePath())) {
            transaction = connection.BeginTransaction();
            foreach (var register in array) {
                string sql = DataBaseUtil.GenerateInsertStatement(register, tableName);
                using (var command = connection.CreateCommand(sql)) {
                    command.ExecuteNonQuery();
                }
            }
            transaction.Commit();
        }
    } catch (Exception e) {
        transaction?.Rollback(); // 异常时回滚事务
        LogUtil.WriteLog(e);
    }
}

总结

这些优化的核心是减少临时对象创建、降低内存占用,同时提升SQLite插入效率。优先尝试SQLite-net的InsertAll方法,这是最简洁高效的方案;如果必须用原生SQL,参数化查询是关键。加上及时释放内存和优化JSON处理,就能解决最后一页卡住的问题。

内容的提问来源于stack exchange,提问作者Diego Macario

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:49