深入数据库同步技术(1)- 基础篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37691493/article/details/84923128

下载网站:www.SyncNavigator.CN 

 客服QQ1793040

----------------------------------------------------------

 

关于HKROnline SyncNavigator 注册机价格的问题


HKROnline SyncNavigator 8.4.1 企业版数据同步软件 自2009年第一个版本开发出来以来,经过8年不断地根据客户需求,加强功能,修复bug,现在已经具备强大的数据库同步功能,以前官方syncnavigator授权码的价格是2800元一套,授权码是绑定电脑硬件的,更换硬件或者电脑,软件无法正常运行,需要重新购买授权码。

今年官方团队有其他项目,没有重点开发市场,其中一个以前官方团队的程序员开发了syncnavigator注册机,用这款注册机也能进行syncnavigator授权激活,功能和以前官方,没有任何影响,只是改变了授权方式。

因为这个版本的syncnavigator注册机是程序员自己开发的,因而成本比以前官方成本要小,并且没有做过多市场开发营销,所以价格相对以前来说优惠很多,这对于有数据同步需求的公司和团队来说,无疑是巨大的福音。

因为这款HKROnline SyncNavigator 软件是目前为止,国内做的最好的数据库同步软件,傻瓜式同步数据库,只需要你设置好来源数据库和目标数据库的账号和密码,一键开启,后台自动同步,断点续传,增量同步,几乎不占内存和CPU资源。并且还支持异构数据库,也可以同步部分表或者部分字段,都可以进行更为精准的设置操作。

未经允许不得转载:syncnavigator数据库同步|syncnavigator 授权码 » 关于HKROnline SyncNavigator 注册机价格的问题

在大约十年前NoSQL概念刚刚提出来的时候,其雄心勃勃的目标就是将关系型数据库(RDB)完全淘汰掉。十年过去了,各类NoSQL产品逐步丰富(K/V,文档,图形,缓存等),它们在跌跌撞撞中找到了自己的定位,其当初的愿景也从“No SQL”演变为“Not Only SQL”。关系型数据库活得甚至比原来更加滋润(当然一些关系型数据库也会居安思危,在产品中加入NoSQL特性,比如Oracle)。

事实上,如果我们的系统需要落地数据,尤其是交易型数据,关系型数据库依然是我们的不二选择。在未来相当长的时间里,我们依然会重度依赖关系型数据库。

而在数据库的使用领域,数据同步绝对是一个绕不开的话题。

本文将以大家常见的数据库Oracle和MySQL为例,介绍如何做好数据同步,更确切地讲,是如何做好表数据同步。

一、数据同步涉及的业务场景

在很多业务场景下,我们都有可能会涉及到数据同步,常见的有:

▪ 基于较单纯的同步目的:

由于业务需要,我们就需要把A库的表同步到B库去,这个目的单一且直接。

▪ 出于构建数据总线目的:

我们知道一般系统中都有一个主数据库,该数据库是一个OLTP库,随时接受终端用户的业务请求,对数据库发生CRUD操作。

如果可以构建一个数据总线(Data Bus),在主数据库有变更事件发生时,可以将数据变更同步/传播到其他系统,比如:Memcached/Redis(更新缓存),ES(更新索引),通知其他业务方(某系统对某些表数据的变更事件很感兴趣)。

数据总线机制可以看做是实现数据发布,数据订阅,数据通知的基础载体。

▪ 其他目的:

我们可以通过数据同步实现Master/Slave,实现去O,或者实现异地机房间的数据同步(为了灾备或构建双主机房)等。

二、数据同步的分类

我们可以从不同维度和层面对数据同步进行分类,比如:

▪ 离线同步和在线同步

离线同步指源库已经脱线,不对外提供服务,这也意味着源库数据不会再发生变化。目前市面上的常见的开源同步工具基本都是面向离线同步,比如阿里的DataX。

在线同步则正好相反,源库依然对应用层系统提供数据服务,我们需要在数据不断发生变化的情况下将变化数据同步到其他目的库。这里面的变化包括插入、更新、删除,甚至包括DDL操作(添加/删除字段、修改字段类型、Truncate操作等),每一类变化都不好处理。

由此可见,在线同步比离线同步要复杂得多。

▪ 全量同步和增量同步

全量同步很好理解,就是将当前可以看到的源表上的所有数据一次性全部同步到目的库表(源表数量可能非常大);而后针对变化的数据,再进行增量同步。

▪ 实时同步、近实时同步和非实时同步

这是从实时性角度的划分,我们当然希望源库变化的数据越快同步到目的库越好。实时性在时间上并没有明确的定义,但一般我们认为基于binlog复制方式的同步是实时的(虽然大多数binlog复制方式是异步发生的)。

对于我们的自研工具da-syncer,在事务量不大的情况下会在1分钟之内将Oracle主站数据同步到其他目的库,可以看做是近实时同步。

非实时同步则对实时性没有太高要求,比如可以是T + 1。

三、数据同步的痛点

无论做何种数据库产品间的数据同步,我们都面临着一些普遍存在的痛点问题,这些痛点问题如果解决不好,会影响数据同步整体准确性和效果。

下面罗列出一些本人遇到的问题,并提供相应解决方案供大家参考。

(1)不同种类数据库字段的类型映射

好的同步工具在做同步任务前会解析源表表结构,并依据源表表结构自动在目的库创建出目的表,然后才进行数据同步操作。

那如何提取出源表的表结构呢?

除了使用JDBC的Metadata接口外,其实更直接更准确的方式是查询源库的元信息表,比如对于Oracle:

SELECT utc.column_id, utc.column_name, t_pk.position, utc.data_type, utc.data_length,

utc.char_length, utc.data_precision, utc.data_scale, utc.nullable

FROM user_tab_columns utc,

(

select ucc.column_name, ucc.position

from user_constraints uc, user_cons_columns ucc

where uc.table_name = ucc.table_name

and uc.constraint_name = ucc.constraint_name

and uc.constraint_type = 'P'

and uc.table_name = ?

) t_pk

where utc.table_name = ?

and utc.column_name = t_pk.column_name(+)

order by t_pk.position asc, utc.column_id asc;

可以将表结构信息,如主键(包括联合主键),数据长度、精度、刻度、是否为null等信息一次性提取到位。对于MySQL也类似,可以从information_schema.columns元信息表提取,这里不再赘述。

如果在同种类数据库间同步数据,则使用上述提取的源表表结构信息直接在目的表建表即可;如果我们需要在不同数据库产品间做数据同步时,则接下来需要知道它们之间字段类型的映射关系,以解决类似Oracle的Number对应MySQL什么数据类型的问题。

通过查阅Oracle官方文档,可以获取这种映射关系https://docs.oracle.com/cd/E12151_01/doc.150/e12155/oracle_mysql_compared.htm#g1034154

(2)主键要求

数据同步对源表上主键的存在性是有要求的,这是因为源表上的每条记录都应该有一个“身份”。尤其在增量同步中,当源表记录发生变化时,我们需要依据这些发生变化的源表记录的“身份”去目的表查找,如果找不到记录就进行插入操作,否则就进行更新操作。

如果同步源是MySQL,则我们可以强制必须存在主键才能进行同步操作,这是因为主键如果不存在,查找会退化成表的全字段匹配,效率非常低下。如果同步源是Oracle,则可以不存在主键,因为我们可以使用Oracle的伪列rowid作为主键。

(3)源表记录获取:游标

在目的表创建完和源表对应的表结构后,我们就可以从源表拉取数据,插入到目的表。

通常情况下我们在应用层程序中进行数据库查询操作,比如在MyBatis中,可以直接使用Mapper注解的方式(@SelectProvider注解),或者将我们的SQL登记在xml中(<select>元素),查询的结果有可能是一个List列表。

默认情况下,上述MyBatis使用的是客户端游标方式(Client Side Cursor),其运行原理是将符合查询条件的记录一次性全部load到客户端虚拟机中形成一个记录集(resultset),在该记录集之上再进行游标的移动。

但是源表记录数有可能非常大(1000万+甚至更多),在做全量同步时需要把全表数据提取出来插入到目的表。这很容易发生OOM内存溢出错误。为了避免该问题发生,我们应该启用服务器端游标(Server Side Cursor):

▪ 如果同步源是MySQL,需要在JDBC URL中添加useCursorFetch=true参数;Oracle则不需要。

▪ 在代码层面同样需要设置(可以在Statement级或更细粒度的ResultSet级):

PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

ps.setFetchSize(500); //可以根据业务情况调整fetch size大小

ps.setFetchDirection(ResultSet.FETCH_FORWARD);

服务器端游标的原理在于我们查询执行完毕后,会在数据库服务器端形成一个只跟当前客户端连接关联的结果集(或者说是记录集快照),客户端可以批量随需从数据库服务器端抓取记录到本地进行处理,而非上述的一次性抓取行为。

这里面有一个坑需要注意一下,对于使用服务器端游标形成的JDBC ResultSet结果集对象,如果结果集中还有记录没有抓取完毕,则关闭该ResultSet对象会出错。

(4)源表分段处理

你可能认为我们在使用服务器端游标后,从源表抓取记录到本地再一步步处理即可。但事实上远非那么简单,其中最重要的一个问题是,服务器端为你生成的记录集快照(Snapshot)并非是永久性的。

比如在Oracle中,为了维持这个快照的数据一致性,需要很多的undo表空间(undo tablespace),尤其是查询大表形成的快照,而该空间通常是多事务共享循环使用的,如果有其他大量事务提交,你的数据很有可能在空间中被覆盖,形成下列错误:

ORA-01555: snapshot too old: rollback segmeng number xxx with name xxx too small

在不依赖于数据库配置的前提下,我们必须在应用层采取必要措施来防止这个问题,我们的解决思路是依据源表主键对源表进行分段,然后再基于分段进行查询处理,以减少出现该问题的几率。

目前da-syncer工具仅仅实现了对Oracle/MySQL源表存在单个主键的大表进行分段。假设大表t1表的主键字段是id,对于Oracle来说,关键的分段语句是:

select max(id) from(select id from t1 where id >= ? order by id asc) where rownum <= ?

对于MySQL来说,关键分段语句是:

select max(id) from (

select @rownum:=@rownum+1 as rn, id

from (select @rownum:=0) r, t1

where id >= ?

order by id asc

) t where rn <= ?

在上述SQL执行时,需要注意的是应该传入和主键类型匹配的JDBC参数类型值,比如,如果主键是char/varchar类型,则应该传入字符串,如果是长整型,则应该传入BigDecimal或Long,否则分段SQL进行主键值比对时将走全表扫描。

举个例子来讲,对于Oracle,如果大表t1上没有主键,则完全可以使用rowid伪列作为主键字段进行分段,但其实rowid伪列的字段类型是ROWID,很多同学喜欢直接where rowid = 'AAAvR2AAUAABsgEAAX'把rowid字段跟字符串比较,这样的查询操作会走全表扫描,效率非常低下,应该使用chartorowid函数进行转换。

上面讲到da-syncer工具只针对具有单个主键的大表进行了分段处理,目前处理的最大的一张表的记录将近4个亿,500万一个分段,分成了80个分段进行处理。

那如果大表是联合主键该怎么分段呢?这个问题可以留给大家思考。

(5)提升同步性能

为了尽快地将数据从源表同步到目的表,以提升同步性能,我们可以考虑以下几个方面:

▪ 查询源表时明确指定按主键排序

我们可能已经注意到,当执行select * from t查询时,无论表上加多少索引,结果集通常是按主键升序排列好的。

这是因为每张数据库表都有一个聚簇索引(假设MySQL使用InnoDB存储引擎),表数据文件本身就是按B+树组织的一个索引结构,这个索引的key是数据表的主键,这棵树的叶节点保存了完整的表记录数据。换句话说,主键值顺序决定了表数据行的真实物理存储顺序。

为了防止源表一些查询可能会打乱主键顺序,如果明确指定按主键排序,并以此顺序插入目的表,则会最大程度地抑制目的表数据文件为了维持B+树的特性而进行的频繁的移动、分裂、旋转、调整,从而降低物理I/O成本。

▪ 批量插入/更新/删除

批量操作也是提高性能的利器,JDBC在Statement/PreparedStatement上提供了batch操作的方法。在此不再赘述。

注意:对于MySQL数据库,需要在JDBC URL中添加rewriteBatchedStatements=true参数才能使batch工作正常,这样batch insert会使用“insert into t (…) values (…), (…), (…)”这样的SQL结构,而batch update/delete也会组合后一次性发往服务器端执行。

另外,通常数据库对一次SQL提交的数据包大小也有限制,比如在MySQL中,可以查询max_allowed_packet参数的使用。

▪ 最后提交

我们不必每批次插入/更新目的表后就commit使数据生效,可以最后commit一次即可。

 (6)增量数据判断:不靠谱的last_modified_date字段

通常情况下,为了判断增量数据,我们会在源表中添加一个“last_modified_date”或类似的字段然后再加上索引,比如在MySQL中:

alter table t1 add column last_modified_date timestamp not null default current_timestamp on update current_timestamp;

当源表数据发生更改时(如发生insert/update操作),记录的last_modified_date会自动发生改变,我们可以把所有last_modified_date大于上次增量同步时间的记录查询出来做同步处理。

这是一个常规做法,但是这个做法是有问题的,其根本原因在于last_modified_date反映了事务的操作时间(什么时候执行的插入/更新操作),而非生效时间(事务commit时间),这两个时间间隔可以相差很久。

举个简单的例子来说明,打开第1个MySQL客户端进行一系列操作(见图1):

1.创建test表

2.开启一个事务

3.插入一条name是name1的记录

4.查询

5.休息10秒钟

6.再次查询

注意,此时事务尚未提交。可以发现4.和6.的查询中last_modified_date值是一样的,它反映了3.中插入这条记录时的操作时间。

图1:第1个MySQL客户端操作演示

打开第2个MySQL客户端进行一系列操作(见图2):

1.查询,因为默认隔离级别是repeatable read,所以看不到客户端1上的未提交记录

2.插入4条新记录

3.查询,只能查询到当前客户端提交的4条新纪录

      图2:第2个MySQL客户端操作演示

假设此时开始做全量同步,则只有客户端2上的4条记录被同步到目的表,再求max(last_modified_date)发现记录5的值最大,将其保存作为下次增量同步的起始值(也就是last_modified_date大于这个值的记录认为发生过变动,需要增量同步)。

将客户端1上的事务提交,这时候客户端2上也会看到这条提交的记录,且记录1的last_modified_date始终未改变(见图3):

图3:客户端1提交后在客户端2上查询

当我们开始增量同步,查询last_modified_date大于记录5的last_modified_date的那些记录时,找不到任何东西,记录1被永远遗弃不能同步了。

所以,last_modified_date字段用于增量同步判断的字段,并不靠谱,但是又不得不使用。在da-syncer工具中,我们会把last_modified_date的时间提前一定时间,比如10分钟,在很大程度上减少了出问题的几率,但是并不能从根本上解决问题。

对于Oracle来说,除了last_modified_date,还可以使用更靠谱的ora_rowscn伪列,它是一个长整型数,反映了事务真正的提交时间,所以可以放心使用。

只不过有一个问题,如果在创建Oracle表时未指定rowdependencies,则默认ora_rowscn伪列是基于Oracle块的,这意味着,即使只插入/更新/删除一条记录,位于同块上的其他记录的ora_rowscn也会变化,这会造成成百上千条无效记录被查询出来同步到目的表,既浪费了服务器资源,又浪费了网络带宽。

在本系列文章的第二部分时序性保障中,我们将介绍如何使用Oracle的闪回版本查询(flashback version query)来避免这个问题。

使用ora_rowscn的第二个问题是不能用到索引,这是一个蛋疼的问题,除非像Linkedin的Databus那样在源表上添加trigger,将ora_rowscn存入其他用户表,再自己建索引。

(7)插入还是更新,这是一个问题

假设我们已经通过last_modified_date或ora_rowscn机制从源表查询出此次增量同步中发生变化的记录,该如何把它们同步到目的表呢?

这些记录发生变化的原因有两种,一种是原来不存在是新插入的,对于这种记录我们应该插入目的表;另外一种就是发生了更改操作,此时我们应该依据主键信息从目的表找到匹配的记录进行更新操作。所以,对于每一条变化的记录,都有一次在目的表中查询记录存不存在的操作,然后再决定是插入还是更新。

幸好目前的数据库可以提供native的SQL把两步合并成一个SQL,如在MySQL中:

insert into … on duplicate key update …;

对于Oracle,有:

merge into … using … when matched then … when not matched then …

(8)数据比对:源表有物理删除怎么办

无论是基于last_modified_date还是ora_rowscn伪列,都不能检测到那些在源表被物理删除的记录。此时,我们必须有独立线程定时查询目的表记录,通过批量从目的表抓取主键列表,然后看看这些主键列表在源表存不存在,如果不存在就在目的表中删除,从而保持两边记录的一致性。

(9)恼人的编码问题:collation和utf8mb4

不同的数据库产品支持不同的字符集和编码。在Oracle中,字符串是区分大小写的,而在MySQL中,默认是不区分的,可以使用如下SQL进行测试:

select 1 from dual where 'a' = 'A';

当我们将数据从Oracle同步到MySQL,为了业务理解上的一致性,我们需要维持这种语义上的连续性,此时,必须在库级别或者表级别设置collation为binary模式,比如utf8_bin。

同时,为了支持emoj表情符,我们需要把MySQL字符集设置为utf8mb4,否则会显示乱码。

以上挑选了几个在数据同步中需要特别注意的点进行介绍,实际上还有其他一些小问题,比如不同数据库有不同的关键字字典,当这些关键字作为表名或字段名的时候必须进行转义处理等等,都是细节性的,就不再赘述了。

在下一部分,我们将带你进入 :

“深入数据库同步技术(2)- 时序性保障”

猜你喜欢

转载自blog.csdn.net/weixin_37691493/article/details/84923128
今日推荐