最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#實(shí)現(xiàn)千萬數(shù)據(jù)秒級(jí)導(dǎo)入的代碼

 更新時(shí)間:2025年09月12日 09:12:46   作者:李袁明  
在實(shí)際開發(fā)中excel導(dǎo)入很常見,現(xiàn)代社會(huì)中很容易遇到大數(shù)據(jù)處理業(yè)務(wù),所以本文我就給大家分享一下千萬數(shù)據(jù)秒級(jí)導(dǎo)入怎么實(shí)現(xiàn),文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下

前言

在實(shí)際開發(fā)中excel導(dǎo)入很常見,現(xiàn)代社會(huì)中很容易遇到大數(shù)據(jù)處理業(yè)務(wù),這里我就給大家分享一下千萬數(shù)據(jù)秒級(jí)導(dǎo)入怎么實(shí)現(xiàn)

一、數(shù)據(jù)存儲(chǔ)

這里使用到的數(shù)據(jù)庫是clickhouse,因?yàn)橄駇ysql、sqlserver這類關(guān)系型數(shù)據(jù)庫對于大數(shù)據(jù)的支持不怎么好所以這里我使用的是clickhouse,它是由俄羅斯的yindex公司開發(fā)的列式存儲(chǔ)數(shù)據(jù)庫。查詢語句這些和mysql差不多上手容易學(xué)起來簡單。需要注意的是clickhouse對數(shù)據(jù)的刪除比較嚴(yán)格一般都不會(huì)去刪除它里面的數(shù)據(jù)庫,如果要?jiǎng)h除數(shù)據(jù),我們一般都是采用用一個(gè)字段標(biāo)記數(shù)據(jù)是否被刪除,然后過濾掉這些被刪除的數(shù)據(jù),clickhouse的四種引擎也可以去了解一下

二、處理邏輯

優(yōu)化前導(dǎo)入耗時(shí)兩分鐘以上優(yōu)化后導(dǎo)入耗時(shí)30秒以內(nèi)

優(yōu)化前代碼處理邏輯

優(yōu)化后導(dǎo)入一千萬數(shù)據(jù)大約需要兩分鐘完成處理。處理邏輯是將數(shù)據(jù)循環(huán)裝入List中,當(dāng)數(shù)據(jù)量達(dá)到預(yù)設(shè)的chunkSize閾值(150萬)時(shí)開始批量插入。值得注意的是,1000萬數(shù)據(jù)需要循環(huán)處理7次。這里存在一個(gè)常見誤區(qū):認(rèn)為提高150萬的閾值能加快處理速度。實(shí)際上,處理速度慢的原因并非循環(huán)次數(shù),而是由于單線程運(yùn)行無法充分發(fā)揮ClickHouse的高并發(fā)優(yōu)勢。

private async Task InsertContentsAsync2(string filePath, BlackListCreateDto dto, ulong maxId, long batchId)
 {
     Stopwatch stopwatch = new();
     var regex = RegexConst.Phone();
     var userId = CurrentUser.Id.ToString();
     var isTxt = filePath.Split(".").Last() == "txt";
     var chunkSize = 1500000;
     List<string> blackLists = new();
     int offset = 0;
     int total = 0;
     stopwatch.Start();
     if (isTxt)
     {
         using StreamReader sr = new(filePath);
         string line;
         while ((line = await sr.ReadLineAsync()) != null)
         {
             if (regex.IsMatch(line))
             {
                 blackLists.Add(line);
                 offset++;
                 total++;
             }
             if (offset >= chunkSize)
             {
                 await InsertDataAsync(blackLists);
                 offset = 0;
                 blackLists.Clear();
             }
         }

         if (offset >= 0)
         {
             await InsertDataAsync(blackLists);
             offset = 0;
             blackLists.Clear();
         }
     }
     else
     {

         var rows = await MiniExcel.QueryAsync(filePath);
         foreach (IEnumerable<dynamic> row in rows)
         {
             var pair = (IDictionary<string, object>)row;
             var cells = pair.Values;
             if (cells != null && cells.Any())
             {
                 foreach (var cell in cells)
                 {
                     if (cell is not null && cell is string str)
                     {
                         blackLists.Add(str);
                         offset++;
                         total++;
                     }

                 }
             }
             if (offset >= chunkSize)
             {
                 await InsertDataAsync(blackLists);
                 offset = 0;
                 blackLists.Clear();
             }
         }

         if (offset >= 0)
         {
             await InsertDataAsync(blackLists);
             offset = 0;
             blackLists.Clear();
         }
     }

     async Task InsertDataAsync(List<string> blackLists)
     {
         List<BlackList> list = blackLists.Select(it => CreateBlackList(dto, batchId, ++maxId, userId, it.ToString())).ToList();
         var result = await _clickHouseClientRepository.BulkInsertAsync(list, _clickHouseTables.Value.SMS_BLACK_LIST_TABLE);
         blackLists.Clear();
     }
     stopwatch.Stop();
     Logger.LogInformation("文件路徑:{filePath},{total}條數(shù)據(jù), 耗時(shí):{Elapsed} ", filePath, total, stopwatch.Elapsed);
 }

優(yōu)化后的代碼

優(yōu)化后的代碼將單線程執(zhí)行模式改為多消費(fèi)者并行處理,采用生產(chǎn)者-消費(fèi)者模式分離IO和計(jì)算操作。文件讀取時(shí)使用最大緩沖區(qū)和順序掃描:

await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920, FileOptions.SequentialScan);

關(guān)鍵優(yōu)化點(diǎn)包括:

  • 使用異步文件讀取(FileStream異步API)
  • 預(yù)分配列表內(nèi)存(new List(chunkSize))
  • 采用ClickHouse批量并行提交而非單條插入
  • 設(shè)置chunkSize為10萬條
  • 使用BoundedChannel進(jìn)行管道容量控制

關(guān)于管道容量控制的作用:

  1. 防止內(nèi)存溢出:限制管道中積壓的未處理數(shù)據(jù)量,避免生產(chǎn)者過快導(dǎo)致內(nèi)存問題
  2. 實(shí)現(xiàn)自動(dòng)背壓(Backpressure):管道滿時(shí)生產(chǎn)者會(huì)自動(dòng)阻塞或丟棄數(shù)據(jù)(取決于配置),直到消費(fèi)者處理部分?jǐn)?shù)據(jù)

選擇10萬而非150萬的原因:

原因1:內(nèi)存考量

  • 150萬條數(shù)據(jù)(假設(shè)每條20字節(jié))單批次占用約30MB
  • 并行處理時(shí)多個(gè)批次會(huì)顯著增加內(nèi)存壓力,可能觸發(fā)GC影響性能
  • 10萬條僅占用約2MB,對GC更友好,適合長期批處理

原因2:ClickHouse性能

  • BulkInsert在5萬~10萬批次時(shí)吞吐量最佳

原因3:并行處理效率

  • 10萬條/批次能更好分配任務(wù)到多線程/核心
  • 150萬條/批次可能導(dǎo)致任務(wù)分配不均

原因4:I/O優(yōu)化

  • 大數(shù)據(jù)批次可能導(dǎo)致I/O阻塞,CPU空閑等待
  • 小批次(10萬)能更好重疊I/O和計(jì)算,提升吞吐量
private async Task InsertContentsAsync(string filePath, BlackListCreateDto dto, ulong maxId, long batchId)
 {
     var stopwatch = Stopwatch.StartNew();
     var regex = RegexConst.Phone();
     var userId = CurrentUser.Id.ToString();
     var isTxt = filePath.EndsWith(".txt", StringComparison.OrdinalIgnoreCase);
     const int chunkSize = 100_000; 
     var totalProcessed = 0L;

     // 并行處理管道
     var processingChannel = Channel.CreateBounded<List<string>>(new BoundedChannelOptions(5)
     {
         SingleWriter = true,
         SingleReader = false,
         FullMode = BoundedChannelFullMode.Wait
     });

     // 啟動(dòng)并行消費(fèi)者
     var processingTasks = Enumerable.Range(0, Environment.ProcessorCount)
         .Select(_ => Task.Run(async () =>
         {
             await foreach (var batch in processingChannel.Reader.ReadAllAsync())
             {
                 await ProcessBatchAsync(batch);
                 Interlocked.Add(ref totalProcessed, batch.Count);
             }
         })).ToArray();

     try
     {
         if (isTxt)
         {
             await ProcessTextFileAsync(filePath, regex, processingChannel.Writer, chunkSize);
         }
         else
         {
             await ProcessExcelFileAsync(filePath, processingChannel.Writer, chunkSize);
         }
     }
     finally
     {
         processingChannel.Writer.Complete();
         await Task.WhenAll(processingTasks);
         stopwatch.Stop();

         Logger.LogInformation("文件路徑:{FilePath}, {Total}條數(shù)據(jù), 耗時(shí):{Elapsed}ms",
             filePath, totalProcessed, stopwatch.ElapsedMilliseconds);
     }

     async Task ProcessBatchAsync(List<string> batch)
     {
         var entities = batch
             .Select(phone => CreateBlackList(dto, batchId, ++maxId, userId, phone))
             .ToList();

         await _clickHouseClientRepository.BulkInsertAsync(
             entities,
             _clickHouseTables.Value.SMS_BLACK_LIST_TABLE);
     }
 }

 private async Task ProcessTextFileAsync(string filePath, Regex regex, ChannelWriter<List<string>> writer, int chunkSize)
 {
     await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920, FileOptions.SequentialScan);
     using var sr = new StreamReader(fs);

     var batch = new List<string>(chunkSize);
     string line;

     while ((line = await sr.ReadLineAsync()) != null)
     {
         if (regex.IsMatch(line))
         {
             batch.Add(line);

             if (batch.Count >= chunkSize)
             {
                 await writer.WriteAsync(batch);
                 batch = new List<string>(chunkSize);
             }
         }
     }

     if (batch.Count > 0)
     {
         await writer.WriteAsync(batch);
     }
 }

 private async Task ProcessExcelFileAsync(string filePath, ChannelWriter<List<string>> writer, int chunkSize)
 {
     await using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read);
     using var reader = MiniExcel.QueryAsDataTable(stream);
     var batch = new List<string>(chunkSize);
     foreach (DataRow row in reader.Rows)
     {
         var cell = row[0].ToString();
         if (cell is string str && !string.IsNullOrWhiteSpace(str))
         {
             batch.Add(str);

             if (batch.Count >= chunkSize)
             {
                 await writer.WriteAsync(batch);
                 batch = new List<string>(chunkSize);
             }
         }
     }

     if (batch.Count > 0)
     {
         await writer.WriteAsync(batch);
     }
 }

總結(jié)

到此這篇關(guān)于C#實(shí)現(xiàn)千萬數(shù)據(jù)秒級(jí)導(dǎo)入的代碼的文章就介紹到這了,更多相關(guān)C#千萬數(shù)據(jù)秒級(jí)導(dǎo)入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • c#禁止通過拖動(dòng),雙擊標(biāo)題欄改變窗體大小的方法

    c#禁止通過拖動(dòng),雙擊標(biāo)題欄改變窗體大小的方法

    今天小編就為大家分享一篇c#禁止通過拖動(dòng),雙擊標(biāo)題欄改變窗體大小的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-12-12
  • C# 使用multipart form-data方式post數(shù)據(jù)到服務(wù)器

    C# 使用multipart form-data方式post數(shù)據(jù)到服務(wù)器

    這篇文章主要介紹了C# 使用multipart form-data方式post數(shù)據(jù)到服務(wù)器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • C# .net core HttpClientFactory用法及說明

    C# .net core HttpClientFactory用法及說明

    這篇文章主要介紹了C# .net core HttpClientFactory用法及說明,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • C#實(shí)現(xiàn)備忘錄功能

    C#實(shí)現(xiàn)備忘錄功能

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)備忘錄功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • C#如何用ThoughtWorks生成二維碼

    C#如何用ThoughtWorks生成二維碼

    這篇文章主要介紹了C#如何用ThoughtWorks生成二維碼,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-08-08
  • WPF自定義路由事件的實(shí)例教程

    WPF自定義路由事件的實(shí)例教程

    &#65279;WPF中有兩種事件模型,一種是在WinForm時(shí)代就存在的CLR事件,另一種是WPF時(shí)代的路由事件,這篇文章主要給大家介紹了關(guān)于WPF自定義路由事件的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • C#實(shí)現(xiàn)基于XML配置MenuStrip菜單的方法

    C#實(shí)現(xiàn)基于XML配置MenuStrip菜單的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)基于XML配置MenuStrip菜單的方法,涉及C#使用XML配置MenuStrip菜單的原理與實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-08-08
  • 一道關(guān)于C#參數(shù)傳遞的面試題分析

    一道關(guān)于C#參數(shù)傳遞的面試題分析

    這篇文章主要介紹了一道關(guān)于C#參數(shù)傳遞的面試題,實(shí)例分析了C#參數(shù)傳遞的相關(guān)使用技巧,需要的朋友可以參考下
    2015-05-05
  • C#二叉搜索樹算法實(shí)現(xiàn)步驟和實(shí)例代碼

    C#二叉搜索樹算法實(shí)現(xiàn)步驟和實(shí)例代碼

    二叉搜索樹(Binary?Search?Tree,簡稱BST)是一種節(jié)點(diǎn)有序排列的二叉樹數(shù)據(jù)結(jié)構(gòu),這篇文章主要介紹了C#二叉搜索樹算法實(shí)現(xiàn)步驟和實(shí)例代碼,需要的朋友可以參考下
    2024-08-08
  • C#圓形頭像框制作并從數(shù)據(jù)庫讀取

    C#圓形頭像框制作并從數(shù)據(jù)庫讀取

    本文主要介紹了C#圓形頭像框制作并從數(shù)據(jù)庫讀取,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08

最新評論

吴堡县| 徐汇区| 宁南县| 阳山县| 宕昌县| 兰西县| 偃师市| 玛沁县| 鞍山市| 饶河县| 旌德县| 顺义区| 西华县| 本溪| 桑日县| 临清市| 香格里拉县| 中阳县| 伊春市| 淳安县| 余庆县| 清苑县| 龙口市| 宜丰县| 滕州市| 嵊泗县| 浦城县| 韶关市| 梅州市| 西充县| 万源市| 贵南县| 台北市| 饶阳县| 沧州市| 凤庆县| 洱源县| 达尔| 汝城县| 古蔺县| 浮梁县|