成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何半天搞定數據庫遷移10億級數據

數據庫 SQL Server
無水平或者垂直切分,但是采用了分區表。分區表策略是按時間降序分的區,將近30個分區。正因為分區表的原因,系統才保證了在性能不是太差的情況下堅持至今。

[[258747]]

一、問題分析

經過幾分鐘的排查,數據庫情況如下:

數據庫采用SQLserver 2008 R2,單表數據量21億

無水平或者垂直切分,但是采用了分區表。分區表策略是按時間降序分的區,將近30個分區。正因為分區表的原因,系統才保證了在性能不是太差的情況下堅持至今。

此表除聚集索引之外,無其他索引,無主鍵(主鍵其實是利用索引來快速查重的)。所以在頻繁插入新數據的情況下,索引調整所耗費的性能比較低。

至于聚集索引和非聚集索引等知識,請各位移步google或者百度。

至于業務,不是太復雜。經過相關人員咨詢,大約40%的請求為單條Insert,大約60%的請求為按class_id 和in_time(倒序)分頁獲取數據。Select請求全部命中聚集索引,所以性能非常高。這也是聚集索引這樣設計的目的。

二、解決問題

由于單表數據量已經超過21億,并且2017年以前的數據幾乎不影響業務,所以決定把2017年以前(不包括2017年)的數據遷移到新表,僅供以后特殊業務查詢使用。經過查詢大約有9億數據量。

數據遷移工作包括三個個步驟:

  • 從源數據表查詢出要遷移的數據
  • 把數據插入新表
  • 把舊表的數據刪除

1、傳統做法

這里申明一點,就算是傳統的做法也需要分頁獲取源數據,因為你的內存一次性裝載不下9億條數據。

1)從源數據表分頁獲取數據,具體分頁條數,太少則查詢原表太頻繁,太多則查詢太慢。

SQL語句類似于:

  1. SELECT * FROM ( 
  2. SELECT *,ROW_NUMBER() OVER(ORDER BY class_id,in_time) p FROM  tablexx WHERE in_time <'2017.1.1'   
  3. ) t WHERE t.p BETWEEN 1 AND 100 

2)把查詢出來的數據插入目標數據表,這里強調一點,一定不要用單條插入策略,必須用批量插入。

3)把數據刪除,其實這里刪除還是有一個小難點,表沒有標示列。這里不展開,因為這不是本文要說的重點。

如果你的數據量不大,以上方法完全沒有問題,但是在9億這個數字前面,以上方法顯得心有余而力不足。一個字:慢,太慢,非常慢。

可以大體算一下,假如每秒可以遷移1000條數據,大約需要的時間為(單位:分):

  1. 900000000/1000/60=15000(分鐘) 

大約需要10天^ V ^

2、改進做法

以上的傳統做法弊端在哪里呢?

  • 在9億數據前查詢必須命中索引,就算是非聚集索引我也不推薦,首推聚集索引。
  • 如果你了解索引的原理,你應該明白,不停的插入新數據的時候,索引在不停的更新,調整,以保持樹的平衡等特性。尤其是聚集索引影響甚大,因為還需要移動實際的數據。

提取以上兩點共同的要素,那就是聚集索引。相應的解決方案也就應運而生:

  • 按照聚集索分頁引查詢數據;
  • 批量插入數據迎合聚集索引,即:按照聚集索引的順序批量插入;
  • 按照聚集索引順序批量刪除;

由于做了表分區,如果有一種方式把2017年以前的分區直接在磁盤物理層面從當前表剝離,然后掛載到另外一個表,可算是神級操作。有誰能指導一下,不勝感激~

三、擴展閱讀

一個表的聚集索引的順序就是實際數據文件的順序,映射到磁盤上,本質上位于同一個磁道上,所以操作的時候磁盤的磁頭不必跳躍著去操作。

存儲在硬盤中的每個文件都可分為兩部分:文件頭和存儲數據的數據區。文件頭用來記錄文件名、文件屬性、占用簇號等信息,文件頭保存在一個簇并映射在FAT表(文件分配表)中。

而真實的數據則是保存在數據區當中的。

平常所做的刪除,其實是修改文件頭的前2個代碼,這種修改映射在FAT表中,就為文件作了刪除標記,并將文件所占簇號在FAT表中的登記項清零,表示釋放空間,這也就是平常刪除文件后,硬盤空間增大的原因。

而真正的文件內容仍保存在數據區中,并未得以刪除。要等到以后的數據寫入,把此數據區覆蓋掉,這樣才算是徹底把原來的數據刪除。如果不被后來保存的數據覆蓋,它就不會從磁盤上抹掉。

四、實際運行代碼

第一步:由于聚集索引需要class_id,所以寧可花2-4秒時間把要操作的class_id查詢出來(ORM為dapper),并且升序排列:

  1. DateTime dtMax = DateTime.Parse("2017.1.1"); 
  2.  var allClassId = DBProxy.GeSourcetLstClassId(dtMax)?.OrderBy(s=>s); 

按照第一步class_id列表順序查詢數據,每個class_id分頁獲取,然后插入目標表,全部完成然后刪除源表相應class_id的數據。

  1. int pageIndex = 1; //頁碼 
  2.            int pageCount = 20000;//每頁的數據條數 
  3.            DataTable tempData =null
  4.            int successCount = 0; 
  5.            foreach (var classId in allClassId) 
  6.            { 
  7.                tempData = null
  8.                pageIndex = 1; 
  9.                while (true
  10.                { 
  11.                    int startIndex = (pageIndex - 1) * pageCount+1; 
  12.                    int endIndex = pageIndex * pageCount; 
  13.                    tempData = DBProxy.GetSourceDataByClassIdTable(dtMax, classId, startIndex, endIndex); 
  14.                    if (tempData == null || tempData.Rows.Count==0) 
  15.                    { 
  16.                        //末尾一頁無數據了,刪除源數據源數據然后跳出 
  17.                         DBProxy.DeleteSourceClassData(dtMax, classId); 
  18.                        break; 
  19.                    } 
  20.                    else 
  21.                    { 
  22.                        DBProxy.AddTargetData(tempData); 
  23.                    } 
  24.                    pageIndex++; 
  25.                } 
  26.                successCount++; 
  27.                Console.WriteLine($"班級:{classId} 完成,已經完成:{successCount}個"); 
  28.            } 

DBProxy完整代碼:

  1. class DBProxy 
  2.     { 
  3.         //獲取要遷移的數據所有班級id 
  4.         public static IEnumerable<int> GeSourcetLstClassId(DateTime dtMax) 
  5.         { 
  6.             var connection = Config.GetConnection(Config.SourceDBStr); 
  7.             string Sql = @"SELECT class_id FROM  tablexx WHERE in_time <@dtMax GROUP BY class_id "
  8.             using (connection
  9.             { 
  10.                 return connection.Query<int>(Sql, new { dtMax = dtMax }, commandType: System.Data.CommandType.Text); 
  11.             } 
  12.         } 
  13.         public static DataTable GetSourceDataByClassIdTable(DateTime dtMax, int classId, int startIndex, int endIndex) 
  14.         { 
  15.             var connection = Config.GetConnection(Config.SourceDBStr); 
  16.             string Sql = @" SELECT * FROM ( 
  17.                         SELECT *,ROW_NUMBER() OVER(ORDER BY in_time desc) p FROM  tablexx WHERE in_time <@dtMax  AND class_id=@classId 
  18.                         ) t WHERE t.p BETWEEN @startIndex AND @endIndex "; 
  19.             using (connection
  20.             { 
  21.                 DataTable table = new DataTable("MyTable"); 
  22.                 var reader = connection.ExecuteReader(Sql, new { dtMax = dtMax, classId = classId, startIndex = startIndex, endIndex = endIndex }, commandType: System.Data.CommandType.Text); 
  23.                 table.Load(reader); 
  24.                 reader.Dispose(); 
  25.                 return table
  26.             } 
  27.         } 
  28.          public static int DeleteSourceClassData(DateTime dtMax, int classId) 
  29.         { 
  30.             var connection = Config.GetConnection(Config.SourceDBStr); 
  31.             string Sql = @" delete from  tablexx WHERE in_time <@dtMax  AND class_id=@classId "
  32.             using (connection
  33.             { 
  34.                 return connection.Execute(Sql, new { dtMax = dtMax, classId = classId }, commandType: System.Data.CommandType.Text); 
  35.             } 
  36.         } 
  37.         //SqlBulkCopy 批量添加數據 
  38.         public static int AddTargetData(DataTable data) 
  39.         { 
  40.             var connection = Config.GetConnection(Config.TargetDBStr); 
  41.             using (var sbc = new SqlBulkCopy(connection)) 
  42.             { 
  43.                 sbc.DestinationTableName = "tablexx_2017";                
  44.                 sbc.ColumnMappings.Add("class_id""class_id"); 
  45.                 sbc.ColumnMappings.Add("in_time""in_time"); 
  46.                 . 
  47.                 . 
  48.                 . 
  49.                 using (connection
  50.                 { 
  51.                     connection.Open(); 
  52.                     sbc.WriteToServer(data); 
  53.                 }                
  54.             } 
  55.             return 1; 
  56.         } 
  57.     } 

運行報告:

程序本機運行,開虛擬專用網絡連接遠程DB服務器,運行1分鐘,遷移的數據數據量為1915560,每秒約3萬條數據。

1915560 / 60=31926 條/秒

CPU情況(不高):

磁盤隊列情況(不高):

五、寫在后面

在以下情況下速度還將提高:

  • 源數據庫和目標數據庫硬盤為ssd,并且分別為不同的服務器;
  • 遷移程序和數據庫在同一個局域網,保障數據傳輸時候帶寬不會成為瓶頸;
  • 合理的設置SqlBulkCopy參數;
  • 我們的場景大多數場景下每次批量插入的數據量達不到設置的值,因為有的class_id 對應的數據量就幾十條,甚至幾條而已,打開關閉數據庫連接也是需要耗時的;
  • 單純的批量添加或者批量刪除操作。

 

責任編輯:武曉燕 來源: 互聯網修煉之道
相關推薦

2024-08-22 14:16:08

2025-02-21 08:20:33

2019-06-05 14:30:21

MySQL數據庫索引

2019-05-27 09:56:00

數據庫高可用架構

2021-02-05 10:58:28

數據存儲架構

2019-05-28 09:31:05

Elasticsear億級數據ES

2023-12-01 15:50:46

2019-09-19 16:29:41

云數據庫遷移DBaaS數據庫

2011-03-03 10:32:07

Mongodb億級數據量

2021-06-29 08:12:22

MySQL數據分頁數據庫

2009-03-19 09:50:25

遷移微軟Analysis Se

2023-10-09 08:37:39

2024-07-17 08:29:20

2023-11-13 08:16:08

MySQL數據數據庫

2015-05-15 14:51:11

TB 級數據云備份

2018-12-14 09:16:31

裝載數據數組

2022-09-25 22:09:09

大數據量技術HDFS客戶端

2021-04-07 13:43:07

PythonDash數據庫

2018-12-14 09:32:06

億級數據存在

2024-02-19 00:06:06

數據分析系統Doris
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产一级大片 | 欧美激情国产日韩精品一区18 | 日韩精品久久久 | 欧美h | 久久久久久高清 | 国产激情视频网 | 狠狠av | 91精品国产乱码久久久久久久久 | 久久久久九九九女人毛片 | 亚洲精品乱 | 久久久久国产精品一区二区 | 91久久国产综合久久 | 久久精品久久久 | 一本综合久久 | 久久久国产一区 | 视频国产一区 | 国产日韩欧美一区二区 | 91精品久久久久 | 操到爽 | 久久久久亚洲国产| 日韩av一区二区在线观看 | 久久久久久国产精品 | 一区二区精品 | 国产免费一区 | 亚洲欧美在线观看视频 | 精品国产18久久久久久二百 | 四虎影院免费在线 | 亚洲永久 | 国产精品亚洲综合 | 欧美一区在线视频 | 日韩精品一区二区三区 | 欧美视频在线播放 | 亚洲精品一区二区在线 | a视频在线| 91精品国产乱码久久久久久久久 | 91视频在线观看 | 亚洲二区在线观看 | 亚洲免费在线播放 | 亚洲一区二区免费看 | 国产精品国产精品国产专区不蜜 | 成人欧美一区二区 |