程序员过关斩将--快速迁移10亿级数据

640?wx_fmt=gif

菜菜呀,咱们业务BJKJ有个表数据需要做迁移

640?wx_fmt=png程序员主力 Y总640?wx_fmt=gif

现在有多少数据?

640?wx_fmt=png菜菜640?wx_fmt=jpeg

大约21亿吧,2017年以前的数据没有业务意义了,给你半天时间把这个事搞定,绩效给你A

640?wx_fmt=png程序员主力 Y总640?wx_fmt=gif

有绩效奖金吗?

640?wx_fmt=png菜菜640?wx_fmt=jpeg

钱的事你去问X总,我当家不管钱

640?wx_fmt=png程序员主力 Y总640?wx_fmt=gif

...........

640?wx_fmt=png菜菜640?wx_fmt=jpeg问题分析

经过几分钟的排查,数据库情况如下:

640?wx_fmt=png1.  数据库采用Sqlserver 2008 R2,单表数据量21亿

640?wx_fmt=png


640?wx_fmt=png2. 无水平或者垂直切分,但是采用了分区表。分区表策略是按时间降序分的区,将近30个分区。正因为分区表的原因,系统才保证了在性能不是太差的情况下坚持至今。

640?wx_fmt=png3. 此表除聚集索引之外,无其他索引,无主键(主键其实是利用索引来快速查重的)。所以在频繁插入新数据的情况下,索引调整所耗费的性能比较低。

640?wx_fmt=png

640?wx_fmt=png640?wx_fmt=png至于聚集索引和非聚集索引等知识,请各位移步google或者百度。640?wx_fmt=png

        至于业务,不是太复杂。经过相关人员咨询,大约40%的请求为单条Insert,大约60%的请求为按class_id 和in_time(倒序)分页获取数据。Select请求全部命中聚集索引,所以性能非常高。这也是聚集索引之所以这样设计的目的。 

解决问题

        由于单表数据量已经超过21亿,并且2017年以前的数据几乎不影响业务,所以决定把2017年以前(不包括2017年)的数据迁移到新表,仅供以后特殊业务查询使用。经过查询大约有9亿数据量。

数据迁移工作包括三个个步骤:

1.  从源数据表查询出要迁移的数据

2.  把数据插入新表

3.  把旧表的数据删除

传统做法

        这里申明一点,就算是传统的做法也需要分页获取源数据,因为你的内存一次性装载不下9亿条数据。

640?wx_fmt=png1.  从源数据表分页获取数据,具体分页条数,太少则查询原表太频繁,太多则查询太慢。

SQL语句类似于

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


640?wx_fmt=png2.  把查询出来的数据插入目标数据表,这里强调一点,一定不要用单条插入策略,必须用批量插入。

640?wx_fmt=png3.  把数据删除,其实这里删除还是有一个小难点,表没有标示列。这里不展开,因为这不是菜菜要说的重点。

        如果你的数据量不大,以上方法完全没有问题,但是在9亿这个数字前面,以上方法显得心有余而力不足。一个字:慢,太慢,非常慢。

可以大体算一下,假如每秒可以迁移1000条数据,大约需要的时间为(单位:分)

900000000/1000/60=15000(分钟)

大约需要10天^ V ^

改进做法

以上的传统做法弊端在哪里呢?

1.  在9亿数据前查询必须命中索引,就算是非聚集索引菜菜也不推荐,首推聚集索引。

2.  如果你了解索引的原理,你应该明白,不停的插入新数据的时候,索引在不停的更新,调整,以保持树的平衡等特性。尤其是聚集索引影响甚大,因为还需要移动实际的数据。


提取以上两点共同的要素,那就是聚集索引。相应的解决方案也就应运而生:

1.  按照聚集索分页引查询数据

2 批量插入数据迎合聚集索引,即:按照聚集索引的顺序批量插入。

3. 按照聚集索引顺序批量删除

由于做了表分区,如果有一种方式把2017年以前的分区直接在磁盘物理层面从当前表剥离,然后挂载到另外一个表,可算是神级操作。有谁能指导一下菜菜,感激不尽

扩展阅读

640?wx_fmt=png1.  一个表的聚集索引的顺序就是实际数据文件的顺序,映射到磁盘上,本质上位于同一个磁道上,所以操作的时候磁盘的磁头不必跳跃着去操作。

640?wx_fmt=png2.  存储在硬盘中的每个文件都可分为两部分:文件头和存储数据的数据区。文件头用来记录文件名、文件属性、占用簇号等信息,文件头保存在一个簇并映射在FAT表(文件分配表)中。而真实的数据则是保存在数据区当中的。平常所做的删除,其实是修改文件头的前2个代码,这种修改映射在FAT表中,就为文件作了删除标记,并将文件所占簇号在FAT表中的登记项清零,表示释放空间,这也就是平常删除文件后,硬盘空间增大的原因。而真正的文件内容仍保存在数据区中,并未得以删除。要等到以后的数据写入,把此数据区覆盖掉,这样才算是彻底把原来的数据删除。如果不被后来保存的数据覆盖,它就不会从磁盘上抹掉。

NetCore 代码(实际运行代码)

1.  第一步:由于聚集索引需要class_id ,所以宁可花2-4秒时间把要操作的class_id查询出来(ORM为dapper),并且升序排列

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

2.  按照第一步class_id 列表顺序查询数据,每个class_id 分页获取,然后插入目标表,全部完成然后删除源表相应class_id的数据。(全部命中聚集索引)

   D int pageIndex = 1//页码
            int pageCount = 20000;//每页的数据条数
            DataTable tempData =null;
            int successCount = 0;
            foreach (var classId in allClassId)
            {
                tempData = null;
                pageIndex = 1;
                while (true)
                {
                    int startIndex = (pageIndex - 1) * pageCount+1;
                    int endIndex = pageIndex * pageCount;

                    tempData = DBProxy.GetSourceDataByClassIdTable(dtMax, classId, startIndex, endIndex);
                    if (tempData == null || tempData.Rows.Count==0)
                    {
                        //最后一页无数据了,删除源数据源数据然后跳出
                         DBProxy.DeleteSourceClassData(dtMax, classId);
                        break;
                    }
                    else
                    {
                        DBProxy.AddTargetData(tempData);
                    }
                    pageIndex++;
                }
                successCount++;
                Console.WriteLine($"班级:{classId} 完成,已经完成:{successCount}个");
            }


DBProxy 完整代码:

class DBProxy
    {
        //获取要迁移的数据所有班级id
        public static IEnumerable<intGeSourcetLstClassId(DateTime dtMax)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @"SELECT class_id FROM  tablexx WHERE in_time <@dtMax GROUP BY class_id ";
            using (connection)
            {
                return connection.Query<int>(Sql, new { dtMax = dtMax }, commandType: System.Data.CommandType.Text);

            }
        }

        public static DataTable GetSourceDataByClassIdTable(DateTime dtMax, int classId, int startIndex, int endIndex)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @" SELECT * FROM (
                        SELECT *,ROW_NUMBER() OVER(ORDER BY in_time desc) p FROM  tablexx WHERE in_time <@dtMax  AND class_id=@classId
                        ) t WHERE t.p BETWEEN @startIndex AND @endIndex "
;
            using (connection)
            {
                DataTable table = new DataTable("MyTable");
                var reader = connection.ExecuteReader(Sql, new { dtMax = dtMax, classId = classId, startIndex = startIndex, endIndex = endIndex }, commandType: System.Data.CommandType.Text);
                table.Load(reader);
                reader.Dispose();
                return table;
            }
        }
         public static int DeleteSourceClassData(DateTime dtMax, int classId)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @" delete from  tablexx WHERE in_time <@dtMax  AND class_id=@classId ";
            using (connection)
            {
                return connection.Execute(Sql, new { dtMax = dtMax, classId = classId }, commandType: System.Data.CommandType.Text);

            }
        }
        //SqlBulkCopy 批量添加数据
        public static int AddTargetData(DataTable data)
        
{
            var connection = Config.GetConnection(Config.TargetDBStr);
            using (var sbc = new SqlBulkCopy(connection))
            {
                sbc.DestinationTableName = "tablexx_2017";               
                sbc.ColumnMappings.Add("class_id""class_id");
                sbc.ColumnMappings.Add("in_time""in_time");
                .
                .
                .
                using (connection)
                {
                    connection.Open();
                    sbc.WriteToServer(data);
                }               
            }
            return 1;
        }

    }

运行报告:

        程序本机运行,开vpn连接远程DB服务器,运行1分钟,迁移的数据数据量为 1915560,每秒约3万条数据

 1915560 / 60=31926 条/秒

 cpu情况(不高):

640?wx_fmt=png


磁盘队列情况(不高):

640?wx_fmt=png

写在最后

在以下情况下速度还将提高

 1. 源数据库和目标数据库硬盘为ssd,并且分别为不同的服务器

 2. 迁移程序和数据库在同一个局域网,保障数据传输时候带宽不会成为瓶颈

 3. 合理的设置SqlBulkCopy参数

 4. 菜菜的场景大多数场景下每次批量插入的数据量达不到设置的值,因为有的class_id 对应的数据量就几十条,甚至几条而已,打开关闭数据库连接也是需要耗时的

 5. 单纯的批量添加或者批量删除操作


640?wx_fmt=png
640?wx_fmt=gif

程序员修仙之路--把用户访问记录优化到极致

程序员修仙之路--把用户访问记录优化到极致

●程序员修仙之路--设计一个实用的线程池●程序员修仙之路--数据结构之CXO让我做一个计算器●程序猿修仙之路--数据结构之设计高性能访客记录系统●程序猿修仙之路--算法之快速排序到底有多快程序猿修仙之路--数据结构之你是否真的懂数组?

互联网之路,菜菜与君一同成长

长按识别二维码关注

640?wx_fmt=jpeg640?wx_fmt=gif


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CodeForces - 76E Points

CodeForces - 76E Points 题意&#xff1a; 给你n个点的坐标&#xff0c;求所有一对点之间的距离的平方和 n<100000 题解&#xff1a; 直接暴力n2肯定不行&#xff0c;我们把这个的式子列出来&#xff1a; 代码&#xff1a; #include<bits/stdc.h> #define deb…

模板:Miller-RabinPollard-Rho(数论)

所谓 pollard-rho&#xff0c;就是泼辣的肉 &#xff08;逃&#xff09; 前言 许多题解都把这两个算法放在了一起。 那我也这样办吧&#xff01; miller-rabin可以在优秀的时间复杂度内完成对一个数的素性检测。 而pollard-rho则是立足于Miler-rabin之上&#xff0c;可以在 …

Asp.NetCore轻松学-部署到 Linux 进行托管

前言上一篇文章介绍了如何将开发好的 Asp.Net Core 应用程序部署到 IIS&#xff0c;且学习了进程内托管和进程外托管的区别&#xff1b;接下来就要说说应用 Asp.Net Core 的特性&#xff08;跨平台&#xff09;&#xff0c;将 .NetCore 部署到 Linux 中&#xff0c;主流的 Linu…

DevC++ 用C语言的多线程 实现简单的客户端和服务器

知识来源一&#xff1a; 使用Dev-C实现简单的客户端和服务器-CSDN博客 此先生的博客使用的是win32 SDK来创建多线程&#xff0c;然后鄙人对这个版本的多线程细节不明。于是又重新用C语言的线程替代win32API,以此继续学习服务器代码。 知识来源二&#xff1a;DevC 多线程创建…

从初创公司的角度来看微服务

在开展微服务的过程中&#xff0c;了解要考虑哪些因素可能是非常有挑战性的事情。没有可以直接使用的金科玉律。每个过程都是不同的&#xff0c;因为每个组织面临的都是不同的环境。在本文中&#xff0c;我将从初创公司的角度分享我们学习到的经验和面临的挑战&#xff0c;以及…

MySQL 集群方案介绍

mysql集群方案这里介绍2种&#xff0c;PXC 和 Replication。大型互联网程序用户群体庞大&#xff0c;所以架构设计单节点数据库已经无法满足需求。大家也深有体会&#xff0c;有一万人在学校网站查成绩或是选课的时候网站时常是访问不了或者相应特别特别慢。这种情况就凸显出来…

ML.NET案例详解:在.NET下使用机器学习API实现化学分子式数据格式的判定

半年前写过一篇类似的文章&#xff0c;题目是&#xff1a;《在.NET中使用机器学习API&#xff08;ML.NET&#xff09;实现化学分子式数据格式的判定》&#xff0c;在该文中&#xff0c;我介绍了化学分子式数据格式的基本知识&#xff0c;同时给出了一个案例&#xff0c;展示了如…

数据结构之线段树合并——永无乡,Lomsat gelral,Tree Rotations,Tree Rotations Escape Through Leaf

文章目录[HNOI2012]永无乡Lomsat gelral「POI2011 R2 Day2」旋转树木 Tree RotationsEscape Through Leaf线段树合并与 fhq-treap合并很类似&#xff0c;也是将两个不同根的线段树暴力合并至于时间复杂度&#xff0c;线段树合并一次是可以达到O(n)O(n)O(n)的&#xff0c;但是大…

吉特仓储管理系统--开源2018年源码

应该说今天过完&#xff0c;这个年就算真正意义上的过完了&#xff0c;没有想到的是又是在出差的路上写这样的文章。废话也不多说&#xff0c;写这篇文章主要的目的是想将去年吉特仓储管理系统开发的一个版本源代码开放出来&#xff0c;供各位开发者阅读使用。github 源代码地址…

自定义Visual Studio.net Extensions 开发符合ABP vnext框架代码生成插件[附源码]

介绍我很早之前一直在做mvc5 scaffolder的开发功能做的已经非常完善,使用代码对mvc5的项目开发效率确实能成倍的提高,就算是刚进团队的新成员也能很快上手,如果你感兴趣 可以参考 http://neozhu.github.io/MVC5-Scaffolder/#/ https://github.com/neozhu/MVC5-Scaffolder但是m…

洛谷P1650:田忌赛马(贪心)

解析 其实并不简单的一道题。 是刘汝佳老师的例题&#xff0c;搜到之后按照讲的策略写了一发。 &#xff08;由于这个策略并不完全正确&#xff0c;就不展开讲了&#xff09; 好啊&#xff01; 可是感觉讲的策略特别对&#xff0c;为什么呢&#xff1f; 原因在于&#xff0…

EFCore Lazy Loading + Inheritance = 干净的数据表 (二)

前言本篇是上一篇EFCore Lazy Loading Inheritance 干净的数据表 &#xff08;一&#xff09; 【献给处女座的DB First程序猿】 前菜 的续篇。这一篇才是真的为处女座的DB First程序猿准备的正餐。继续上一篇的话题&#xff0c;我们希望用EFCore&#xff0c;且继续使用与逻辑…

我们为什么要搞长沙.NET技术社区?

某种意义上讲&#xff0c;长沙和中国大部分内地城市一样&#xff0c;都是互联网时代的灯下黑。没有真正意义上的互联网公司&#xff0c;例如最近发布的中国互联网企业一百强中没有一家湖南或者长沙的公司就是明证。然而长沙并非没有互联网人&#xff0c;在麓谷几十万计的IT 从业…

在ASP.NET Core中使用EPPlus导入出Excel文件

这篇文章说明了如何使用EPPlus在ASP.NET Core中导入和导出.xls/.xlsx文件&#xff08;Excel&#xff09;。在考虑使用.NET处理excel时&#xff0c;我们总是寻找第三方库或组件。使用Open Office Xml格式&#xff08;xlsx&#xff09;读取和写入Excel 2007/2010文件的最流行的.n…

月旦评 之 DevOps招贤令2019 - 没有人比我们更懂DevOps

公元164-182年间&#xff0c;汝南平舆的许氏兄弟于每月初一品评人物&#xff0c;褒贬时政&#xff0c;被称为“月旦评”。所谓“子治世之能臣&#xff0c;乱世之奸雄也”这句许邵评价曹操的话也是来自于“月旦评”&#xff1b;时间一下子来到了2019年&#xff0c;DevOps招贤令再…

HDU - 2204 Eddy‘s爱好(尚未完全解决)

HDU - 2204 Eddy’s爱好 题意&#xff1a; 给你一个正整数N&#xff0c;确定在1到N之间有多少个可以表示成M^K&#xff08;K>1)的数 题解&#xff1a; 参考题解&#xff1a; 我们先举例找找规律 1~10以内2的次方有多少个&#xff1f;有121,224,329,一共三个&#xff0c;…

EF Core中避免贫血模型的三种行之有效的方法(翻译)

[Paul Hiles: 3 ways to avoid an anemic domain model in EF Core &#xff1a;https://www.devtrends.co.uk/blog/3-ways-to-avoid-an-anemic-domain-model-in-ef-core]1.引言在使用ORM中&#xff08;比如Entity Framework&#xff09;贫血领域模型十分常见 。本篇文章将先探…

Saving Beans HDU - 3037(卢卡斯定理)

Saving Beans HDU - 3037&#xff08;卢卡斯定理&#xff09; 题意&#xff1a; 他们想知道有多少种方法可以在n树中保存不超过m个bean&#xff08;它们是相同的&#xff09;。 现在他们求助于你&#xff0c;你应该给他们答案。 结果可能非常巨大; 你应该输出模p的结果&…

我们为什么要搞长沙.NET技术社区(三)

我们为什么要搞长沙.NET技术社区&#xff08;三&#xff09; 小饭局搞事情先从饭局开始是中华民族的优良传统。昨天晚餐时间&#xff0c;长沙 .net 技术社区的主要发起人员进行了一番小聚&#xff0c;同时也作为一个非正式会议&#xff0c;对社区发展进行了探讨。从介绍自己对于…

【招聘(北京)】北森测评招聘 .NET 架构师、高级工程师

工作职责公司核心产品的迭代需求分析设计开发。公司核心产品的线上维护和性能调优。对初中级技术人员培养和质量把关。编写软件设计和技术文档。任职资格为人正直、诚信、责任心强&#xff0c;能承受较大工作压力。强烈的目标导向意识&#xff0c;逻辑思维清晰&#xff0c;执行…