自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何半天搞定數(shù)據(jù)庫(kù)遷移10億級(jí)數(shù)據(jù)

數(shù)據(jù)庫(kù) SQL Server
無(wú)水平或者垂直切分,但是采用了分區(qū)表。分區(qū)表策略是按時(shí)間降序分的區(qū),將近30個(gè)分區(qū)。正因?yàn)榉謪^(qū)表的原因,系統(tǒng)才保證了在性能不是太差的情況下堅(jiān)持至今。

[[258747]]

一、問(wèn)題分析

經(jīng)過(guò)幾分鐘的排查,數(shù)據(jù)庫(kù)情況如下:

數(shù)據(jù)庫(kù)采用SQLserver 2008 R2,單表數(shù)據(jù)量21億

無(wú)水平或者垂直切分,但是采用了分區(qū)表。分區(qū)表策略是按時(shí)間降序分的區(qū),將近30個(gè)分區(qū)。正因?yàn)榉謪^(qū)表的原因,系統(tǒng)才保證了在性能不是太差的情況下堅(jiān)持至今。

此表除聚集索引之外,無(wú)其他索引,無(wú)主鍵(主鍵其實(shí)是利用索引來(lái)快速查重的)。所以在頻繁插入新數(shù)據(jù)的情況下,索引調(diào)整所耗費(fèi)的性能比較低。

至于聚集索引和非聚集索引等知識(shí),請(qǐng)各位移步google或者百度。

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

二、解決問(wèn)題

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

數(shù)據(jù)遷移工作包括三個(gè)個(gè)步驟:

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

1、傳統(tǒng)做法

這里申明一點(diǎn),就算是傳統(tǒng)的做法也需要分頁(yè)獲取源數(shù)據(jù),因?yàn)槟愕膬?nèi)存一次性裝載不下9億條數(shù)據(jù)。

1)從源數(shù)據(jù)表分頁(yè)獲取數(shù)據(jù),具體分頁(yè)條數(shù),太少則查詢?cè)硖l繁,太多則查詢太慢。

SQL語(yǔ)句類(lèi)似于:

  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)把查詢出來(lái)的數(shù)據(jù)插入目標(biāo)數(shù)據(jù)表,這里強(qiáng)調(diào)一點(diǎn),一定不要用單條插入策略,必須用批量插入。

3)把數(shù)據(jù)刪除,其實(shí)這里刪除還是有一個(gè)小難點(diǎn),表沒(méi)有標(biāo)示列。這里不展開(kāi),因?yàn)檫@不是本文要說(shuō)的重點(diǎn)。

如果你的數(shù)據(jù)量不大,以上方法完全沒(méi)有問(wèn)題,但是在9億這個(gè)數(shù)字前面,以上方法顯得心有余而力不足。一個(gè)字:慢,太慢,非常慢。

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

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

大約需要10天^ V ^

2、改進(jìn)做法

以上的傳統(tǒng)做法弊端在哪里呢?

  • 在9億數(shù)據(jù)前查詢必須命中索引,就算是非聚集索引我也不推薦,首推聚集索引。
  • 如果你了解索引的原理,你應(yīng)該明白,不停的插入新數(shù)據(jù)的時(shí)候,索引在不停的更新,調(diào)整,以保持樹(shù)的平衡等特性。尤其是聚集索引影響甚大,因?yàn)檫€需要移動(dòng)實(shí)際的數(shù)據(jù)。

提取以上兩點(diǎn)共同的要素,那就是聚集索引。相應(yīng)的解決方案也就應(yīng)運(yùn)而生:

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

由于做了表分區(qū),如果有一種方式把2017年以前的分區(qū)直接在磁盤(pán)物理層面從當(dāng)前表剝離,然后掛載到另外一個(gè)表,可算是神級(jí)操作。有誰(shuí)能指導(dǎo)一下,不勝感激~

三、擴(kuò)展閱讀

一個(gè)表的聚集索引的順序就是實(shí)際數(shù)據(jù)文件的順序,映射到磁盤(pán)上,本質(zhì)上位于同一個(gè)磁道上,所以操作的時(shí)候磁盤(pán)的磁頭不必跳躍著去操作。

存儲(chǔ)在硬盤(pán)中的每個(gè)文件都可分為兩部分:文件頭和存儲(chǔ)數(shù)據(jù)的數(shù)據(jù)區(qū)。文件頭用來(lái)記錄文件名、文件屬性、占用簇號(hào)等信息,文件頭保存在一個(gè)簇并映射在FAT表(文件分配表)中。

而真實(shí)的數(shù)據(jù)則是保存在數(shù)據(jù)區(qū)當(dāng)中的。

平常所做的刪除,其實(shí)是修改文件頭的前2個(gè)代碼,這種修改映射在FAT表中,就為文件作了刪除標(biāo)記,并將文件所占簇號(hào)在FAT表中的登記項(xiàng)清零,表示釋放空間,這也就是平常刪除文件后,硬盤(pán)空間增大的原因。

而真正的文件內(nèi)容仍保存在數(shù)據(jù)區(qū)中,并未得以刪除。要等到以后的數(shù)據(jù)寫(xiě)入,把此數(shù)據(jù)區(qū)覆蓋掉,這樣才算是徹底把原來(lái)的數(shù)據(jù)刪除。如果不被后來(lái)保存的數(shù)據(jù)覆蓋,它就不會(huì)從磁盤(pán)上抹掉。

四、實(shí)際運(yùn)行代碼

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

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

按照第一步class_id列表順序查詢數(shù)據(jù),每個(gè)class_id分頁(yè)獲取,然后插入目標(biāo)表,全部完成然后刪除源表相應(yīng)class_id的數(shù)據(jù)。

  1. int pageIndex = 1; //頁(yè)碼 
  2.            int pageCount = 20000;//每頁(yè)的數(shù)據(jù)條數(shù) 
  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.                        //末尾一頁(yè)無(wú)數(shù)據(jù)了,刪除源數(shù)據(jù)源數(shù)據(jù)然后跳出 
  17.                         DBProxy.DeleteSourceClassData(dtMax, classId); 
  18.                        break; 
  19.                    } 
  20.                    else 
  21.                    { 
  22.                        DBProxy.AddTargetData(tempData); 
  23.                    } 
  24.                    pageIndex++; 
  25.                } 
  26.                successCount++; 
  27.                Console.WriteLine($"班級(jí):{classId} 完成,已經(jīng)完成:{successCount}個(gè)"); 
  28.            } 

DBProxy完整代碼:

  1. class DBProxy 
  2.     { 
  3.         //獲取要遷移的數(shù)據(jù)所有班級(jí)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 批量添加數(shù)據(jù) 
  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.     } 

運(yùn)行報(bào)告:

程序本機(jī)運(yùn)行,開(kāi)虛擬專(zhuān)用網(wǎng)絡(luò)連接遠(yuǎn)程DB服務(wù)器,運(yùn)行1分鐘,遷移的數(shù)據(jù)數(shù)據(jù)量為1915560,每秒約3萬(wàn)條數(shù)據(jù)。

1915560 / 60=31926 條/秒

CPU情況(不高):

磁盤(pán)隊(duì)列情況(不高):

五、寫(xiě)在后面

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

  • 源數(shù)據(jù)庫(kù)和目標(biāo)數(shù)據(jù)庫(kù)硬盤(pán)為ssd,并且分別為不同的服務(wù)器;
  • 遷移程序和數(shù)據(jù)庫(kù)在同一個(gè)局域網(wǎng),保障數(shù)據(jù)傳輸時(shí)候帶寬不會(huì)成為瓶頸;
  • 合理的設(shè)置SqlBulkCopy參數(shù);
  • 我們的場(chǎng)景大多數(shù)場(chǎng)景下每次批量插入的數(shù)據(jù)量達(dá)不到設(shè)置的值,因?yàn)橛械腸lass_id 對(duì)應(yīng)的數(shù)據(jù)量就幾十條,甚至幾條而已,打開(kāi)關(guān)閉數(shù)據(jù)庫(kù)連接也是需要耗時(shí)的;
  • 單純的批量添加或者批量刪除操作。

 

責(zé)任編輯:武曉燕 來(lái)源: 互聯(lián)網(wǎng)修煉之道
相關(guān)推薦

2024-08-22 14:16:08

2025-02-21 08:20:33

2019-06-05 14:30:21

MySQL數(shù)據(jù)庫(kù)索引

2019-05-27 09:56:00

數(shù)據(jù)庫(kù)高可用架構(gòu)

2019-05-28 09:31:05

Elasticsear億級(jí)數(shù)據(jù)ES

2023-12-01 15:50:46

2021-02-05 10:58:28

數(shù)據(jù)存儲(chǔ)架構(gòu)

2019-09-19 16:29:41

云數(shù)據(jù)庫(kù)遷移DBaaS數(shù)據(jù)庫(kù)

2011-03-03 10:32:07

Mongodb億級(jí)數(shù)據(jù)量

2021-06-29 08:12:22

MySQL數(shù)據(jù)分頁(yè)數(shù)據(jù)庫(kù)

2009-03-19 09:50:25

遷移微軟Analysis Se

2023-10-09 08:37:39

2015-05-15 14:51:11

TB 級(jí)數(shù)據(jù)云備份

2024-07-17 08:29:20

2021-04-07 13:43:07

PythonDash數(shù)據(jù)庫(kù)

2023-11-13 08:16:08

MySQL數(shù)據(jù)數(shù)據(jù)庫(kù)

2019-07-09 08:23:07

數(shù)據(jù)安全旅游網(wǎng)絡(luò)安全

2018-12-14 09:32:06

億級(jí)數(shù)據(jù)存在

2018-12-14 09:16:31

裝載數(shù)據(jù)數(shù)組

2011-09-23 09:09:38

數(shù)據(jù)庫(kù)遷移
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)