|
|
November 27 本部分屏幕录像如下: http://www.sybase.com.cn/gvswse/site/china/content.jsp?_doc_id=1958
每当 MobiLink 同步服务器在同步过程中遇到错误时都执行handle_error连接事件。如果未定义 handle_error 脚本或此脚本导致错误,则缺省动作代码是 3000:回退当前事务并取消当前同步。
通常情况下,每个 SQL 操作只会出现一个错误,但也可能出现多个。在一组错误中每出现一次错误时都会调用一次 handle_error
脚本。传递到第一个错误的动作代码是 3000,后续调用是在前一次调用所返回的动作代码中传递的。MobiLink 将使用多个调用所返回的值中的最大值。
您可以修改脚本中的动作代码并返回一个值以指示 MobiLink 如何继续。动作代码参数可以使用以下数值:
handle_error 事件的 SQL 脚本必须作为存储过程执行。
如果错误是作为同步的一部分发生的,则提供用户名。否则该值为空。
如果在操作某一特定表时发生错误,则提供表名称。否则该值为空。表名称是客户端应用程序中表的名称。该名称在统一数据库中是否存在直接对应的名称取决于同步系统的设计。动作代码将告知 MobiLink 同步服务器下一步该做什么。在调用此脚本之前,MobiLink
同步服务器将动作代码设置为缺省值,该缺省值的大小取决于错误的严重程度。您可以使用脚本修改该值。脚本必须返回或设置一个动作代码。 本部分的脚本如下: 对remote:
CREATE PUBLICATION Lab_06 ( TABLE emp )
CREATE SYNCHRONIZATION USER "demo06"
CREATE SYNCHRONIZATION SUBSCRIPTION TO Lab_06 FOR "demo06" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion='demo06'
对center:
CALL ml_add_table_script( 'demo06', 'emp', 'download_cursor', 'SELECT * FROM empxxxxxxx' );
COMMIT
ALTER PROCEDURE MLHandleError( OUT @action INTEGER, IN @error_code INTEGER, IN @error_message VARCHAR(1000), IN @user_name VARCHAR(128), IN @table_name VARCHAR(128)) BEGIN COMMIT; SET @action = 1000; END
CALL ml_add_connection_script( 'demo06', 'handle_error', 'CALL MLHandleError(?,?,?,?,?)')
COMMIT
November 07
本部分的屏幕录像如下:
http://www.sybase.com.cn/gvswse/site/china/content.jsp?_doc_id=1956
要解决冲突问题,我们应首先理解何谓冲突。
在向统一数据库中上载行时可能会发生冲突。如果两个用户修改不同远程数据库中相同的行,则当这两行中的第二个到达 MobiLink 同步服务器时将出现冲突。在冲突发生时,您应定义一个过程以计算正确值,或至少使用日志文件记录冲突。
在同步的下载阶段,远程数据库中不会出现冲突。如果下载的行中包含一个新的主键,则该行的值将插入到新的一行。如果该主键与一个现有行的主键相匹配,则会更新该行中的值。
冲突与错误是两个不同的概念。冲突处理是应用程序应该包含的一部分。
只有在更新行的过程中才会检测到冲突。尝试更新自上一次同步后已更新或删除的行将会导致冲突。缺省情况下:
这些是为方便操作而定义的内置行为。如果需要不同的行为,可以使用一个或多个上载事件自行实现。
我们在demo的前半部分展示了有冲突而未解决的情况。当两个不同的客户端分别将相同的数据进行不同更改后,再在不同时间上载,后上载的数据成了最终结果。这可能是我们不想要的情况。我们需要对冲突进行一定的检测、然后再根据我们的用户规则进行处理。
在 MobiLink 客户端向 MobiLink 同步服务器发送一个更新后的行时,发送的数据中不仅包含该行的新值(后映像),而且还将包含旧行值的副本(前映像)。如果前映像与统一数据库中的当前值不匹配,表示检测到了冲突。
检测冲突的方法有两种:
如果为同一个表定义这两个脚本,则只使用 upload_fetch 脚本。
仅当使用 upload_fetch 脚本或适当的 upload_update 脚本时,MobiLink 同步服务器才检测冲突。如果提供了 upload_fetch 脚本,MobiLink 同步服务器会将上载的前映像与 upload_fetch 脚本返回的包含相同主键值的行值进行比较。如果存在前映像中的值与当前统一数据库中的值不匹配的情况,则表示 MobiLink 同步服务器检测到了冲突。
我们这里用upload_fetch:
CALL ml_add_table_script( 'remote1', 'upload_fetch', 'SELECT empno, empname, gender, deptno, last_modified FROM emp WHERE empno = ?' ) 出现冲突时,可以使用临时表(或永久表)和 resolve_conflict 脚本加以解决。
例如,我们可以先建两个临时表,然后在upload_old_row_insert表事件中把统一数据库的旧值插入专门存放旧值的临时表,然后再upload_new_row_insert表事件中把想要更新的值插入专门存放新值的临时表。接下来就可以在resolve_conflict表事件中解决冲突了。
当然,除了通过resolve_conflict事件解决冲突,我们也可利用upload_update、end_upload来解决冲突。
本部分的脚本如下:
先建立两个目录:
remote1 & remote2。
把数据库分别拷贝。
启动个数据库,中心叫center,其余两个一个叫remote1,一个叫remote2。
dbeng9 center.db
dbeng9 -n remote1 remote.db
dbeng9 -n remote2 remote.db
重新建立remote1和remote2的ODBC
对3个库分别执行: ALTER TABLE emp ADD last_modified TIMESTAMP DEFAULT TIMESTAMP
dbisql:
remote1:
CREATE PUBLICATION Lab_05 ( TABLE emp )
CREATE SYNCHRONIZATION USER "remote1"
CREATE SYNCHRONIZATION SUBSCRIPTION TO Lab_05 FOR "remote1" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion='remote1'
remote2:
CREATE PUBLICATION Lab_05 ( TABLE emp )
CREATE SYNCHRONIZATION USER "remote2"
ALTER SYNCHRONIZATION SUBSCRIPTION TO Lab_05 FOR "remote2" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion='remote1'
center:
CALL ml_add_table_script('remote1', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE last_modified > ?');
CALL ml_add_table_script('remote1', 'emp', 'upload_update', 'UPDATE emp SET empname=?, gender=?, deptno=? WHERE empno=?');
COMMIT
命令行下:
dbmlsrv9 -c "dsn=center" -v+ -x tcpip -dl -o mlserver.mls -zu+
dbmlsync -c "dsn=remote1" -v -o dbmlsync1.out -e "sv=remote1"
dbmlsync -c "dsn=remote2" -v -o dbmlsync2.out -e "sv=remote1"
对remote2:
UPDATE emp SET deptno = '0005' WHERE empno = 1
COMMIT
再次同步remote2
dbmlsync -c "dsn=remote2" -v -o dbmlsync2.out -e "sv=remote1"
对remote1
UPDATE emp SET deptno = '0002' WHERE empno = 1
COMMIT
同步remote1
dbmlsync -c "dsn=remote1" -v -o dbmlsync1.out -e "sv=remote1"
对center:
CREATE GLOBAL TEMPORARY TABLE emp_new( empno INTEGER NOT NULL PRIMARY KEY, empname VARCHAR(20), gender BIT, deptno CHAR(4), last_modified TIMESTAMP); CREATE GLOBAL TEMPORARY TABLE emp_old( empno INTEGER NOT NULL PRIMARY KEY, empname VARCHAR(20), gender BIT, deptno CHAR(4), last_modified TIMESTAMP)
CALL ml_add_connection_script( 'remote1', 'end_upload', 'DROP TABLE emp_new; DROP TABLE emp_old');
CALL ml_add_table_script( 'remote1', 'upload_fetch', 'SELECT empno, empname, gender, deptno, last_modified FROM emp WHERE empno = ?' )
CALL ml_add_table_script( 'remote1', 'emp', 'upload_old_row_insert', 'INSERT INTO emp_old VALUES(?,?,?,?)');
CALL ml_add_table_script( 'remote1', 'emp', 'upload_new_row_insert', 'INSERT INTO emp_new VALUES(?,?,?,?)');
CALL ml_add_table_script( 'remote1', 'emp', 'resolve_conflict', 'CALL ResolveConflictDemo()');
COMMIT;
ALTER OR REPLACE PROCEDURE ResolveConflictDemo() BEGIN UPDATE emp e SET e.deptno = en.deptno FROM emp_new en WHERE e.empno = en.empno AND en.deptno < e.deptno; DELETE FROM emp_new; DELETE FROM emp_old; END;
本部分的屏幕录像如下:
http://www.sybase.com.cn/gvswse/site/china/content.jsp?_doc_id=1944
在上一部分中,我们看见在download_cursor表事件中有两个参数,其中我们使用了ml_username这个参数。那么,last_download这个参数是怎么使用的呢?我们注意到,last_download这个参数是TIMESTAMP类型的。它指明了上一次下载的时间。我们可以通过比较时间戳,来达到每次仅同步下载自上次下载以来更改过的数据。在下载阶段紧前面的最后一次成功的同步过程中,从统一数据库中获取的时间值即为 last_download
时间戳。如果当前用户从未成功地进行过同步,则该值将被设置为 1900-01-01。
在真正实施比较之前,我们需要在统一数据库一侧做一些改动,也就是在一些表上增加一个时间戳的字段。比如,我们在录像中对统一数据库实施了以下语句:
ALTER TABLE emp ADD last_modified TIMESTAMP NOT NULL DEFAULT TIMESTAMP
对于不同的统一数据库来说,这个字段略有不同:
Oracle的映射数据类型为date IBM DB2的映射数据类型为timestamp MS SQL Server的映射数据类型为datetime Sybase ASE的映射数据类型为datetime
时间戳方法是可以进行高效的同步的最实用的通用技术。此项技术涉及跟踪每个用户上次进行同步的时间,并使用此信息控制下载到每个远程数据库的行。
MobiLink 保留了一个用以说明每个 MobiLink
用户上一次下载数据的时间戳值。该值被称为上次下载的时间戳。上次下载的时间戳将作为一个参数被提供给许多事件,该时间戳还可以在同步脚本中使用。
如果您正在使用 Adaptive Server Anywhere 统一数据库,而且保存上次修改信息的列的类型为 DEFAULT
TIMESTAMP,则不应同步该列。如果您的远程数据库需要这样的列,应使用不同的列名。否则,时间戳的缺省值可能被上载值覆盖,并不会包含上次在统一数据库中修改该行的时间。当我们在统一数据库中的表中增加了时间戳后,就可以在download_cursor中编写如下的脚本: CALL ml_add_table_script( 'demo4', 'emp', 'download_cursor', 'SELECT empno, empname, gender, deptno FROM emp WHERE last_modified > ?')
所以,上述download_cursor的脚本中将只下载自上次同步后更改的数据。 本部分的代码如下: center:
ALTER TABLE emp ADD last_modified TIMESTAMP NOT NULL DEFAULT TIMESTAMP
Remote:
CREATE PUBLICATION Lab_04 ( TABLE emp(empno, empname, gender, deptno) )
CREATE SYNCHRONIZATION USER "Sales04"
CREATE SYNCHRONIZATION SUBSCRIPTION TO Lab_04 FOR "Sales04" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion = 'demo4'
Center:
CALL ml_add_table_script( 'demo4', 'emp', 'download_cursor', 'SELECT empno, empname, gender, deptno FROM emp WHERE last_modified > ?')
COMMIT
cmd:
dbmlsrv9 -dl -v+ -x tcpip -c "dsn=center" -o mlserver.mls -zu+
dbmlsync -o dbmlsync.out -v -e "sv=demo4" -c "dsn=remote"
October 24 本部分的屏幕录像如下: http://www.sybase.com.cn/gvswse/site/china/content.jsp?_doc_id=1943
上一部分,我们了解了MobiLink的大致工作机理,并且介绍了一些事件和事件内的脚本。上一部分中,有一个脚本是这样的:CALL ml_add_table_script('demo2', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE deptno=''0001''');它是为版本为demo2的脚本建立了一个对emp表下载时的命令,要求之下载符合deptno = '0001'的数据。但是我们来想象一个情况,我们往往都是喜欢灵活的一些SQL。例如,不同部门的人下载不同的数据,而且可能要求下载或者上传自上次同步以来的数据,即增量同步。这两种需求都可以通过我们的数据分区来实现。也就是说,我们把数据中心的数据按照一定的规则划分,每次只下载/上传部分数据。
在这一部分,我们先看如何将数据分区的第一部分:根据用户分区数据。
在很多事件里,尤其是表事件,都会有一个事件参数,叫ml_username。它是一个VARCHAR(128)的变量,MobiLink的用户名会传入该事件中,我们即可使用该变量以达到数据分区的效果。我们来看一个例子,例如download_cursor表事件,里面有两个事件参数,第一个是last_download,TIMESTAMP数据类型;第二个参数是ml_username,VARCHAR(128)数据类型。这两个参数都可以在事件脚本中用?代替。同步时,MobiLink会将参数一一传送给?。所以,第一个?接收第一个参数,第二个?接收第二个参数。参看这个语句:
CALL ml_add_table_script( 'demo3', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE ? IS NOT NULL AND deptno = ?');
其中,?IS NOT NULL中的?是用来接收last_download这个参数的;第二个问号,也就是deptno = ?是用来接收ml_username的。所以,在以上语句中,download_cursor仅会让deptno符合ml_username的数据下载。其他很多事件中都有这个参数。具体参考《MobiLink 同步技术用户指南》,在http://www.sybase.com.cn/gvswse/site/china/sdn/iAnyDOCIndex.jsp下载。
所以,在此之前,你可以建立一个用户,叫0002:CREATE SYNCHRONIZATION USER "0002"
然后将该用户添加至MobiLink服务器端:dbmluser -c "dsn=center" -u 0002 -p abcdefg
在同步的时候,MobiLink服务器端不要开启-zu+开关,客户端触发同步时,分别用-u和-mp参数指定用户名和口令:dbmlsync -c "dsn=remote" -o dbmlsync.out -v -u 0002 -mp abcdefg -e "sv=demo3"。
这样,我们只需要建立不同的同步用户,让不同的人使用不同的用户名/口令登录,就能够达到数据分区的效果。
这个部分的脚本如下:
cmd:
dbeng9 center.db dbeng9 remote.db
dbisql:
Remote:
CREATE PUBLICATION Lab_03 ( TABLE emp )
CREATE SYNCHRONIZATION USER "0002"
CREATE SYNCHRONIZATION SUBSCRIPTION TO Lab_03 FOR "0002" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion = 'demo3'
center:
CALL ml_add_table_script( 'demo3', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE ? IS NOT NULL AND deptno = ?');
COMMIT
cmd:
dbmluser -c "dsn=center" -u 0002 -p abcdefg
dbmlsrv9 -dl -v+ -x tcpip -c "dsn=center" -o mlserver.mls
dbmlsync -c "dsn=remote" -o dbmlsync.out -v -u 0002 -mp abcdefg -e "sv=demo3"
October 23 第二部分的屏幕录像如下:
http://www.sybase.com.cn/gvswse/site/china/content.jsp?_doc_id=1941
在“SQL Anywhere简介15”中,我们演示了一个最简单的数据同步情景。由此,我们有几点总结:
* MobiLink 由服务器端(dbmlsrv9)和客户端(dbmlsync)组成。服务器端与数据中心连接,客户端与远程数据库连接。其中,数据中心可以是任何符合 ODBC标准的数据源,例如Oracle、IBM DB2、MS SQLServer与Sybase ASE等。 * MobiLink在同步的过程中首先将远程数据库的数据上载,接着将数据中心的数据下载,以此完成一次同步。 * 在非常简单的情况下,不需要编写任何脚本。
但是情况总是会变化的。我们可能有特殊要求。例如,我们只需要某一个部门的数据,我们不想把所有的更改都上传到服务器上……那么,通过什么来指挥MobiLink上载或者下发哪些数据呢?就通过在统一服务器端编写的MobiLink的事件和事件中的脚本来控制。
首先,MobiLink的事件分为两大类, 第一大类是连接事件,这类事件在全局范围内指挥MobiLink的行为。第二类事件是表事件,这类事件在某表的范围内指挥MobiLink的行为。MobiLink在不同的阶段触发这些事件,书写在这些事件里面的脚本就能指挥MobiLink的行为。
有关于MobiLink事件的文档,可以通过http://www.sybase.com.cn/gvswse/site/china/sdn/iAnyDOCIndex.jsp网页里面的《MobiLink 同步技术用户指南》获得。我们在这里只讲一部分内容。
连接事件:
begin_connection:连接统一数据库后调用 begin_synchronization:在远程连接后,执行任何数据改变前调用 (参数:ml_username) begin_upload:在将远程数据库的更改执行到统一数据库之前调用。它是upload事务的一部分(参数:ml_username) end_upload:在将远程数据库的更改执行完成后调用。upload(上载)阶段随之提交(参数:ml_username) begin_download:在将数据从统一数据库抽取出来送至远程数据库前调用。它是下载事务的一部分(参数:last_download, ml_username) end_download:在远程数据库实施改变后调用。下载事务随之提交(参数:last_download, ml_username) end_synchronization:在执行完改变后与远程断开连接前调用(参数:ml_username) end_connection:在与统一数据库断开连接前调用
两个特殊的连接事件:
handle_error report_error
表事件:
begin_upload:在远程每张表执行上载到统一数据库之前调用。(参数:ml_username, table) upload_insert:对每一行将送至统一数据库的插入调用。(参数:col1, col2, col3...) upload_update:对每一行将送至统一数据库的更新调用。(参数:SET col1, col2, ..., colN WHERE pk1, pk2, ..., pkN) upload_delete:对每一行将送至统一数据库的删除调用。(参数:pk1, pk2, ..., pkN) end_upload:在将远程表的更改执行至统一数据库后调用。(参数:ml_username, table) begin_download:在统一数据库将每张表的数据下载前调用。(参数:last_download, ml_username, table) download_cursor:每张表调用一次,将统一数据库的表数据下载至远程数据库。(参数:last_download, ml_username) end_download:在统一数据库将每张表的数据下载后调用。(参数:last_download, ml_username, table)
需要注意的是,在连接事件和表事件中有一些重复的事件,例如begin_upload,不过它们作用的级别不一样,触发的时间也不一样。对于所有的连接事件,我们都用ml_add_connection_script存储过程来实现;对于所有的表事件,我们都用ml_add_table_script存储过程来实现。
例如,call ml_add_connection_script('Lab', 'begin_download', 'CALL InitiateDownload()'),第一个参数是代表脚本版本,第二个参数指明连接事件的名称,第三个参数指定连接事件触发时的逻辑,在这里是调用一个存储过程。再看一个表事件的例子:call ml_add_table_script('demo2', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE deptno=''0001'''),第一个参数代表脚本版本,第二个参数指明该事件跟哪个事件相关,第三个参数指明事件的名称,第四个参数是事件触发时执行的语句。在这里,我们指定服务器只将emp中满足deptno='0001'的数据下载至远程。
upload_delete、upload_update和upload_insert里面的参数都是用?传递的。详细信息,请参看《MobiLink 同步技术用户指南》。
第二部分屏幕录像的代码如下: 将数据库文件拷贝过来
dbeng9 center.db
dbeng9 remote.db
dbisql
对于remote
CREATE PUBLICATION Lab_02 ( TABLE emp )
CREATE SYNCHRONIZATION USER "Sales02"
CREATE SYNCHRONIZATION SUBSCRIPTION TO Lab_02 FOR "Sales02" TYPE 'TCPIP' ADDRESS 'host=localhost;port=2439' OPTION ScriptVersion='demo2'
对center: CALL ml_add_table_script('demo2', 'emp', 'upload_delete', 'DELETE emp WHERE empno=?');
CALL ml_add_table_script('demo2', 'emp', 'download_cursor', 'SELECT * FROM emp WHERE deptno=''0001''');
在控制台下 dbmluser -c "dsn=center" -u Sales02 -p abcdefg
dbmlsrv9 -dl -v+ -x tcpip -c "dsn=center" -o mlserver.mls
dbmlsync -c "dsn=remote" -o dbmlsync.out -v -u Sales02 -mp abcdefg -e "sv=demo2" 其中,sv是ScriptVersion的简写。
|
|
|
|