面试官:"谈谈分库分表吧?"

 

原文连接:面试官:"谈谈分库分表吧?"mysql

 

面试官:“有并发的经验没?” 面试

应聘者:“有一点。”  算法

面试官:“那大家为了处理并发,作了哪些优化?”  spring

应聘者:“先后端分离啊,限流啊,分库分表啊。。”  sql

面试官:"谈谈分库分表吧?"  数据库

应聘者:“bala。bala。bala。。”  后端

一、分库分表的缘由

一、随着单库中的数据量愈来愈大,相应的,查询所须要的时间也愈来愈多,至关于数据的处理遇到了瓶颈
二、单库发生意外的时候,须要修复的是全部的数据,而多库中的一个库发生意外的时候,只须要修复一个库(固然,也能够用物理分区的方式处理这种问题)服务器

 

二、分库分表的经常使用策略

2.1 垂直切分:

根据业务的不一样,将原先拥有不少字段的表拆分为两个或者多个表,这样的代价我我的以为很大,原来对这应这个表的关系,开始细分,须要必定的重构,并且随着数据量的增多,极有可能还要增长水平切分;mybatis

 

2.2 水平切分:

数据表结构,将数据分散在多个表中;架构

 

1.有瑕疵的简单分库分表(按id的大小分库分表)

按照分片键(咱们这里就用id表示了)的大小来进行分库分表,若是你的id是自增的,并且能保证在进行分库分表后也是自增的,那么能进行很好的改造,以id大小水平切分,并且极有可能不用迁移数据。

固然,这里只列举了比较小的数据量,实际状况的分库的界限仍是要依据具体的状况而定。这样的分库分表,由于新的数据总在一个库里,极可能致使热点过于集中(读写可能集中在一个库中),这是采起这种方式须要考虑的事情。
若是没法保证你的id是自增加的,那么你的数据就会凌乱的分散在各个数据库,这样热点确实就分散了,但是每当你增长一个数据库的时候,你就有可能进行大量的数据迁移,应对这种状况,就是尽可能减小数据迁移的代价,因此这里运用一致性hash的方式进行分库分表比较好,能够尽量的减小数据迁移,而且也能让解决热点过于集中的问题。一致性hash的分库策略去百度一下或者谷歌一下应该很容易搜到。
这里按id的大小来分库,还能够发散到按照时间来分库,好比说一个月的数据放在一个库,这个使用mycat比较容易实现按时间分库,不过你须要思考的数据的离散性,数据集中于一个两月,而剩下的几个月数据稀疏,这样的又可能须要按照数据的生产规律合并几个月到一个库中,使得数据分布均匀。

 

2.比较方便的取模分库

通常的取模分库分表是就是将id mod n,而后放入数据库中,这样可以使数据分散,不会有热点的问题,那么,剩下的是,在扩容的时候,是否会有数据迁移的问题,通常的扩容,固然是会有数据迁移的。

取模.PNG


例子中,对3取模,当须要扩容的时候(假设增长两个库),则对5取模,这样的结果必然是要进行数据迁移的,可是能够运用一些方法,让它不进行数据迁移,scale-out扩展方案可以避免在取模扩容的时候进行数据迁移。

 

(1)第一种扩容的方式:根据表的数据增长库的数量

首先,咱们有一个数据库——DB_0,四张表——tb_0,tb_1,tb_2,tb_3
那么咱们如今数据到数据库是这样的:
DB="DB_0"
TB=“tb_"+id%4

 

 

当数据增长,须要进行扩容的时候,我增长一个数据库DB_1
DB="DB_"+((id%4)/2)
TB=“tb_"+id%4

 


当咱们的数据继续飙升,这个时候又须要咱们增长库,这个时候进行加库操做的时候,就不是增长一个库,而必须是两个,这样才能保证不进行数据迁移。
DB="DB_"+id%4
TB=“tb_"+id%4


这个时候到了这个方案的加库上限,不能继续加库了,不然就要进行数据迁移,因此这个方案的弊端仍是挺大了,这样的方式,也应该会形成单表的数据量挺大的。

 

(2)第二种扩容的方式:成倍的增长表

首先,咱们仍是一个数据库——DB_0,两张表——tb_0,tb_1
那么咱们如今数据到数据库是这样的:
DB="DB_0"
TB=“tb_"+id%2

 

 

假设当咱们数据量打到一千万的时候,咱们增长一个库,这时须要咱们增长两张表tb_0_1,tb_1_1,而且原来的DB_0中库的表tb_1整表迁移到DB_1中,tb_0和tb_0_1放在DB_0中,tb_1和tb_1_1放到DB1中。
DB="DB_"+id%2
tb:
if(id<1千万)    {  return  "tb_" + id %  2 }
else if(id>=1千万)   {   return "tb_"+ id % 2 + "_1" }

 

数据的增加不可能到此为止,当增长到两千万的时候,咱们须要加库,这个时候,按照这种作法,咱们须要增长两个库(DB_2,DB_3)和四张表(tb_0_2,tb_1_2,tb_2_2,tb_3_2),将上次新增的表整表分别放进两个新的库中,而后每一个库里再生成一张新表。
DB:
if(id < 1千万)    {  return  "DB_" + id %  2 }
else if(1千万 <= id < 2千万)   {   return  "DB_"+ id % 2 +2 }
else if(2千万 <= id )   {   return  "DB_"+ id % 4 }
tb:
if(id < 1千万)    {  return  "tb_" + id %  2 }
else if(1千万 <= id < 2千万)   {   return "tb_"+ id % 2 +"1" }
else if(id >= 2千万) { return "tb
"+ id % 4+"_2" }

 

值得注意的一点,在id超出范围的时候,该给怎么样的提示是值得思考的。

 

三、经常使用的分库分表中间件

3.1 简单易用的组件:

  • 当当sharding-jdbc

  • 蘑菇街TSharding

3.2 强悍重量级的中间件:

  • sharding

  • TDDL Smart Client的方式(淘宝)

  • Atlas(Qihoo 360)

  • alibaba.cobar(是阿里巴巴(B2B)部门开发)

  • MyCAT(基于阿里开源的Cobar产品而研发)

  • Oceanus(58同城数据库中间件)

  • OneProxy(支付宝首席架构师楼方鑫开发)

  • vitess(谷歌开发的数据库中间件)

四、分库分表须要解决的问题

一、事务问题

解决事务问题目前有两种可行的方案:分布式事务和经过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。

方案一:使用分布式事务
  • 优势: 交由数据库管理,简单有效

  • 缺点:性能代价高,特别是shard愈来愈多时

方案二:由应用程序和数据库共同控制
  • 原理:将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并经过应用程序来总控 各个小事务。

  • 优势:性能上有优点

  • 缺点:须要应用程序在事务控制上作灵活设计。若是使用 了spring的事务管理,改动起来会面临必定的困难。

二、跨节点Join的问题

只要是进行切分,跨节点Join的问题是不可避免的。可是良好的设计和切分却能够减小此类状况的发生。解决这一问题的广泛作法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求获得关联数据。

三、跨节点的count,order by,group by以及聚合函数问题

这些是一类问题,由于它们都须要基于所有数据集合进行计算。多数的代理都不会自动处理合并工做。解决方案:与解决跨节点join问题的相似,分别在各个节点上获得结果后在应用程序端进行合并。和join不一样的是每一个结点的查询能够并行执行,所以不少时候它的速度要比单一大表快不少。但若是结果集很大,对应用程序内存的消耗是一个问题。

四、数据迁移,容量规划,扩容等问题

来自淘宝综合业务平台团队,它利用对2的倍数取余具备向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,可是依然须要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来讲,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。

五、事务

5.1 分布式事务
  • 参考: 
    关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究

  • 优势
    基于两阶段提交,最大限度地保证了跨数据库操做的“原子性”,是分布式系统下最严格的事务实现方式。
    实现简单,工做量小。因为多数应用服务器以及一些独立的分布式事务协调器作了大量的封装工做,使得项目中引入分布式事务的难度和工做量基本上能够忽略不计。

  • 缺点
    系统“水平”伸缩的死敌。基于两阶段提交的分布式事务在提交事务时须要在多个节点之间进行协调,最大限度地推后了提交事务的时间点,客观上延长了事务的执行时间,这会致使事务在访问共享资源时发生冲突和死锁的几率增高,随着数据库节点的增多,这种趋势会愈来愈严重,从而成为系统在数据库层面上水平伸缩的"枷锁", 这是不少Sharding系统不采用分布式事务的主要缘由。


基于Best Efforts 1PC模式的事务


参考spring-data-neo4j的实现。鉴于Best Efforts 1PC模式的性能优点,以及相对简单的实现方式,它被大多数的sharding框架和项目采用

5.2 事务补偿(幂等值)

对于那些对性能要求很高,但对一致性要求并不高的系统,每每并不苛求系统的实时一致性,只要在一个容许的时间周期内达到最终一致性便可,这使得事务补偿机制成为一种可行的方案。事务补偿机制最初被提出是在“长事务”的处理中,可是对于分布式系统确保一致性也有很好的参考意义。笼统地讲,与事务在执行中发生错误后当即回滚的方式不一样,事务补偿是一种过后检查并补救的措施,它只指望在一个允许时间周期内获得最终一致的结果就能够了。事务补偿的实现与系统业务紧密相关,并无一种标准的处理方式。一些常见的实现方式有:对数据进行对账检查;基于日志进行比对;按期同标准数据来源进行同步,等等。

六、ID问题

一旦数据库被切分到多个物理结点上,咱们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID没法保证在全局上是惟一的;另外一方面,应用程序在插入数据以前须要先得到ID,以便进行SQL路由.
一些常见的主键生成策略

6.1 UUID

使用UUID做主键是最简单的方案,可是缺点也是很是明显的。因为UUID很是的长,除占用大量存储空间外,最主要的问题是在索引上,在创建索引和基于索引进行查询时都存在性能问题。

结合数据库维护一个Sequence表

此方案的思路也很简单,在数据库中创建一个Sequence表,表的结构相似于:

1CREATE TABLE `SEQUENCE` (  
2    `table_name` varchar(18) NOT NULL,  
3    `nextid` bigint(20) NOT NULL,  
4    PRIMARY KEY (`table_name`)  
5) ENGINE=InnoDB

每当须要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。此方案也较简单,但缺点一样明显:因为全部插入任何都须要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将没法工做。有人提出使用Master-Slave进行主从同步,但这也只能解决单点问题,并不能解决读写比为1:1的访问压力问题。

6.2 Twitter的分布式自增ID算法Snowflake

在分布式系统中,须要生成全局UID的场合仍是比较多的,twitter的snowflake解决了这种需求,实现也仍是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。

  • 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
    在上面的字符串中,第一位为未使用(实际上也可做为long的符号位),接下来的41位为毫秒级时间,而后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),而后12位该毫秒内的当前毫秒内的计数,加起来恰好64位,为一个Long型。

这样的好处是:总体上按照时间自增排序,而且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID做区分),而且效率较高,经测试,snowflake每秒可以产生26万ID左右,彻底知足须要。

七、跨分片的排序分页

通常来说,分页时须要按照指定字段进行排序。当排序字段就是分片字段的时候,咱们经过分片规则能够比较容易定位到指定的分片,而当排序字段非分片字段的时候,状况就会变得比较复杂了。为了最终结果的准确性,咱们须要在不一样的分片节点中将数据进行排序并返回,并将不一样分片返回的结果集进行汇总和再次排序,最后再返回给用户。以下图所示:

上面图中所描述的只是最简单的一种状况(取第一页数据),看起来对性能的影响并不大。可是,若是想取出第10页数据,状况又将变得复杂不少,以下图所示:

有些读者可能并不太理解,为何不能像获取第一页数据那样简单处理(排序取出前10条再合并、排序)。其实并不难理解,由于各分片节点中的数据多是随机的,为了排序的准确性,必须把全部分片节点的前N页数据都排序好后作合并,最后再进行总体的排序。很显然,这样的操做是比较消耗资源的,用户越日后翻页,系统性能将会越差。

那如何解决分库状况下的分页问题呢?有如下几种办法:

若是是在前台应用提供分页,则限定用户只能看前面n页,这个限制在业务上也是合理的,通常看后面的分页意义不大(若是必定要看,能够要求用户缩小范围从新查询)。

若是是后台批处理任务要求分批获取数据,则能够加大page size,好比每次获取5000条记录,有效减小分页数(固然离线访问通常走备库,避免冲击主库)。

分库设计时,通常还有配套大数据平台汇总全部分库的记录,有些分页查询能够考虑走大数据平台。

八、分库策略

分库维度肯定后,如何把记录分到各个库里呢?

8.1 两种方式:
  • 根据数值范围,好比用户Id为1-9999的记录分到第一个库,10000-20000的分到第二个库,以此类推。

  • 根据数值取模,好比用户Id mod n,余数为0的记录放到第一个库,余数为1的放到第二个库,以此类推。

优劣比较:

评价指标按照范围分库按照Mod分库
库数量前期数目比较小,能够随用户/业务按需增加前期即根据mode因子肯定库数量,数目通常比较大
访问性能前期库数量小,全库查询消耗资源少,单库查询性能略差前期库数量大,全库查询消耗资源多,单库查询性能略好
调整库数量比较容易,通常只需为新用户增长库,老库拆分也只影响单个库困难,改变mod因子致使数据在全部库之间迁移
数据热点新旧用户购物频率有差别,有数据热点问题新旧用户均匀到分布到各个库,无热点
实践中,为了处理简单,选择mod分库的比较多。同时二次分库时,为了数据迁移方便,通常是按倍数增长,好比初始4个库,二次分裂为8个,再16个。这样对于某个库的数据,一半数据移到新库,剩余不动,对比每次只增长一个库,全部数据都要大规模变更。
补充下,mod分库通常每一个库记录数比较均匀,但也有些数据库,存在超级Id,这些Id的记录远远超过其余Id,好比在广告场景下,某个大广告主的广告数可能占整体很大比例。若是按照广告主Id取模分库,某些库的记录数会特别多,对于这些超级Id,须要提供单独库来存储记录。

九、分库数量

分库数量首先和单库能处理的记录数有关,通常来讲,Mysql 单库超过5000万条记录,Oracle单库超过1亿条记录,DB压力就很大(固然处理能力和字段数量/访问模式/记录长度有进一步关系)。

在知足上述前提下,若是分库数量少,达不到分散存储和减轻DB性能压力的目的;若是分库的数量多,好处是每一个库记录少,单库访问性能好,但对于跨多个库的访问,应用程序须要访问多个库,若是是并发模式,要消耗宝贵的线程资源;若是是串行模式,执行时间会急剧增长。

最后分库数量还直接影响硬件的投入,通常每一个分库跑在单独物理机上,多一个库意味多一台设备。因此具体分多少个库,要综合评估,通常初次分库建议分4-8个库。

十、路由透明

分库从某种意义上来讲,意味着DB schema改变了,必然影响应用,但这种改变和业务无关,因此要尽可能保证分库对应用代码透明,分库逻辑尽可能在数据访问层处理。固然彻底作到这一点很困难,具体哪些应该由DAL负责,哪些由应用负责,这里有一些建议:

对于单库访问,好比查询条件指定用户Id,则该SQL只需访问特定库。此时应该由DAL层自动路由到特定库,当库二次分裂时,也只要修改mod 因子,应用代码不受影响。

对于简单的多库查询,DAL负责汇总各个数据库返回的记录,此时仍对上层应用透明。

十一、使用框架仍是自主研发

目前市面上的分库分表中间件相对较多,其中基于代理方式的有MySQL Proxy和Amoeba,基于Hibernate框架的是Hibernate Shards,基于jdbc的有当当sharding-jdbc,基于mybatis的相似maven插件式的有蘑菇街的蘑菇街TSharding,经过重写spring的ibatis template类是Cobar Client,这些框架各有各的优点与短板,架构师能够在深刻调研以后结合项目的实际状况进行选择,可是总的来讲,我我的对于框架的选择是持谨慎态度的。一方面多数框架缺少成功案例的验证,其成熟性与稳定性值得怀疑。另外一方面,一些从成功商业产品开源出框架(如阿里和淘宝的一些开源项目)是否适合你的项目是须要架构师深刻调研分析的。固然,最终的选择必定是基于项目特色、团队情况、技术门槛和学习成本等综合因素考量肯定的。

五、如何部署

停机部署法 

大体思路就是,挂一个公告,半夜停机升级,而后半夜把服务停了,跑数据迁移程序,进行数据迁移。

步骤以下:

(1)出一个公告,好比“今晚00:00~6:00进行停机维护,暂停服务”
(2)写一个迁移程序,读db-old数据库,经过中间件写入新库db-new1db-new2,具体以下图所示

(3)校验迁移先后一致性,没问题就切该部分业务到新库。

顺便科普一下,这个中间件。如今流行的分库分表的中间件有两种,一种是proxy形式的,例如mycat,是须要额外部署一台服务器的。还有一种是client形式的,例如当当出的Sharding-JDBC,就是一个jar包,使用起来十分轻便。我我的偏向Sharding-JDBC,这种方式,无需额外部署,无其余依赖,DBA也无需改变原有的运维方式。

评价:

你们不要以为这种方法low,我其实一直以为这种方法可靠性很强。并且我相信各位读者所在的公司必定不是什么很牛逼的互联网公司,若是大家的产品凌晨1点的用户活跃数还有超过1000的,大家握个爪!毕竟不是全部人都在什么电商公司的,大部分产品半夜都没啥流量。因此此方案,并不是没有可取之处。

可是此方案有一个缺点,累!不止身体累,心也累!你想一想看,原本定六点结束,你五点把数据库迁移好,可是不知怎么滴,程序切新库就是有点问题。因而,眼瞅着天就要亮了,赶忙把数据库切回老库。第二个晚上继续这么干,简直是身心俱疲。

ps:这里教你们一些技巧啊,若是你真的没作过度库分表,又想吹一波,涨一下工资,建议答这个方案。由于这个方案比较low,low到没什么东西能够深挖的,因此答这个方案,比较靠谱。

你刚才恰好有提到分库分表的相关问题,咱们当时部署的时候,先停机。而后半夜迁移数据,而后次日将流量切到新库,这种方案太累,不知道贵公司有没有什么更好的方案?

那么这种状况下,面试官会有两种回答。第一种,面试官硬着头皮随便扯。第二种,面试官真的作过,据实回答。记住,面试官怎么回答的不重要。重点的是,你这个问题出去,会给面试官一种错觉:"这个小伙子真的作过度库分表。"

若是你担忧进去了,真派你去作分库分表怎么办?OK,不要怕。我赌你试用期碰不到这个活。由于能进行分库分表,一定对业务很是熟。还在试用期的你,一定对业务不熟,若是领导给你这种活,我只能说他有一颗大心脏。

ok,指点到这里。面试原本就是一场斗智斗勇的过程,扯远了,回到咱们的主题。

双写部署法(一)

这个就是不停机部署法,这里我须要先引进两个概念:历史数据增量数据

假设,咱们是对一张叫作test_tb的表进行拆分,由于你要进行双写,系统里头和test_tb表有关的业务以前一定会加入一段双写代码,同时往老库和新库中写,而后进行部署,那么

历史数据:在该次部署前,数据库表test_tb的有关数据,咱们称之为历史数据。
增量数据:在该次部署后,数据库表test_tb的新产生的数据,咱们称之为增量数据。

而后迁移流程以下

(1)先计算你要迁移的那张表的max(主键)。在迁移过程当中,只迁移db-oldtest_tb表里,主键小等于该max(主键)的值,也就是所谓的历史数据。

这里有特殊状况,若是你的表用的是uuid,无法求出max(主键),那就以建立时间做为划分历史数据和增量数据的依据。若是你的表用的是uuid,又没有建立时间这个字段,我相信机智的你,必定有办法区分出历史数据和增量数据。

(2)在代码中,与test_tb有关的业务,多加一条往消息队列中发消息的代码,将操做的sql发送到消息队列中,至于消息体如何组装,你们自行考虑。须要注意的是,只发写请求的sql,只发写请求的sql,只发写请求的sql。重要的事情说三遍!
缘由有二:

(1)只有写请求的sql对恢复数据才有用。

(2)系统中,绝大部分的业务需求是读请求,写请求比较少。

注意了,在这个阶段,咱们不消费消息队列里的数据。咱们只发写请求,消息队列的消息堆积状况不会太严重!

(3)系统上线。另外,写一段迁移程序,迁移db-oldtest_tb表里,主键小于该max(主键)的数据,也就是所谓的历史数据。

上面步骤(1)~步骤(3)的过程以下


等到db-old中的历史数据迁移完毕,则开始迁移增量数据,也就是在消息队列里的数据。
(4)将迁移程序下线,写一段订阅程序订阅消息队列中的数据
(5)订阅程序将订阅到到数据,经过中间件写入新库
(6)新老库一致性验证,去除代码中的双写代码,将涉及到test_tb表的读写操做,指向新库。
上面步骤(4)~步骤(6)的过程以下

这里你们可能会有一个问题,在步骤(1)~步骤(3),系统对历史数据进行操做,会形成不一致的问题么?

OK,不会。这里咱们对delete操做和update操做作分析,由于只有这两个操做才会形成历史数据变更,insert进去的数据都是属于增量数据。

(1)对db-oldtest_tb表的历史数据发出delete操做,数据还未删除,就被迁移程序给迁走了。此时delete操做在消息队列里还有记录,后期订阅程序订阅到该delete操做,能够进行删除。

(2)对db-oldtest_tb表的历史数据发出delete操做,数据已经删除,迁移程序迁不走该行数据。此时delete操做在消息队列里还有记录,后期订阅程序订阅到该delete操做,再执行一次delete,并不会对一致性有影响。
update的操做相似,不赘述。

双写部署法(二)

上面的方法有一个硬伤,注意我有一句话

(2)在代码中,与test_tb有关的业务,多加一条往消息队列中发消息的代码,将操做的sql发送到消息队列中,至于消息体如何组装,你们自行考虑。

你们想一下,这么作,是否是形成了严重的代码入侵。将非业务代码嵌入业务代码,这么作,后期删代码的时候特别累。

有没什么方法,能够避免这个问题的?

有的,订阅binlog日志。关于binlog日志,我尽可能下周写一篇《研发应该掌握的binlog知识》,这边我就介绍一下做用

记录全部数据库表结构变动(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。binlog不会记录SELECT和SHOW这类操做,由于这类操做对据自己并无修改。

还记得咱们在双写部署法(一)里介绍的,往消息队列里发的消息,都是写操做的消息。而binlog日志记录的也是写操做。因此订阅该日志,也能知足咱们的需求。

因而步骤以下

(1)打开binlog日志,系统正常上线就好
(2)仍是写一个迁移程序,迁移历史数据。步骤和上面相似,不啰嗦了。
步骤(1)~步骤(2)流程图以下


(3)写一个订阅程序,订阅binlog(mysql中有canal。至于oracle中,你们就随缘本身写吧)。而后将订阅到的数据经过中间件,写入新库。
(4)检验一致性,没问题就切库。
步骤(3)~步骤(4)流程图以下

 

怎么验数据一致性

这里大概介绍一下吧,这篇的篇幅太长了,你们内心有底就行。

(1)先验数量是否一致,由于验数量比较快。

至于验具体的字段,有两种方法:

(2.1)有一种方法是,只验关键性的几个字段是否一致。

(2.2)还有一种是 ,一次取50条(不必定50条,具体本身定,我只是举例),而后像拼字符串同样,拼在一块儿。用md5进行加密,获得一串数值。新库同样如法炮制,也获得一串数值,比较两串数值是否一致。若是一致,继续比较下50条数据。若是发现不一致,用二分法肯定不一致的数据在0-25条,仍是26条-50条。以此类推,找出不一致的数据,进行记录便可。

 

END

相关文章
相关标签/搜索