上载时,对于一行不能包含多个操作(插入、更新或删除)。但可将多个操作组合为一个上载操作;例如,如果要插入一行,然后进行更新,可用一个插入最终值的操作替代这两个操作。
上载应用于统一数据库时,将首先应用插入和更新操作,然后应用删除操作。对于给定表中的操作顺序,不能进行任何其它假设。
在多个同步之间在多个数据库上更新行时会发生冲突。MobiLink 服务器可识别冲突,因为上载中的每个更新操作均包含要更新行的前映像。前映像是上次成功上载或下载时该行中所有列的值。如果应用上载时前映像与统一数据库中的值不匹配,MobiLink 服务器便会认为发现冲突。
如果您的应用程序需要冲突检测,并且您使用脚本式上载,则需要在远程数据库上跟踪每行在上次成功上载或下载时的值。这样您就可以上载正确的前映像。
维护前映像数据的一个方法是创建与同步表完全相同的前映像表。然后可在同步表上创建一个触发器,每次执行更新时均填充前映像表。成功上载后,可删除前映像表中的行。
有关实现冲突解决的示例,请参见脚本式上载示例。
如果不需要处理冲突检测,则可以不跟踪前映像,这样可大大简化应用程序。您可以改用插入操作上载更新。即在统一数据库上编写 upload_insert 脚本,使该脚本在行不存在时插入行,在行已存在时更新行。如果使用 SQL Anywhere 统一数据库,可通过在 upload_insert 脚本的 INSERT 语句中使用 ON EXISTING 子句来实现此功能。
请参见INSERT 语句。
不处理冲突时,如果两个或多个远程数据库更改同一行,则最后同步的数据库将覆盖之前的更改。
对于删除操作,上载行的主键必须正确。然而,在大多数情况下,非主键列的值是否与统一数据库中的值匹配并不重要。只有在一种情况下非主键列的值很重要,就是在 MobiLink 服务器上使用了强制冲突模式。在这种情况下,所有列值均传送到统一数据库上的 upload_old_row_insert 脚本。根据实现此脚本的方式不同,非主键列值可能必须要正确。
请参见强制冲突。
使用 dbmlsync 扩展选项 LockTables 的缺省设置,可使 dbmlsync 在构建上载之前获得所有同步表的独占锁,从而避免许多与脚本式上载相关的问题。这可防止其它连接在您的脚本构建上载时更改同步表。还可确保所有未提交事务均不影响在您的脚本构建上载期间打开的同步表。
如果必须关闭表锁定,请参见没有表锁定的脚本式上载。
在大多数情况下,每个操作要在远程数据库上正好上载一次。为帮助实现这个目的,MobiLink 为每个预订维护一个进度值。缺省情况下,进度值是 dbmlsync 开始构建最后一个成功上载的时间。使用 sp_hook_dbmlsync_set_upload_end_progress 挂接,可用其它值覆盖此进度值。
请参见sp_hook_dbmlsync_set_upload_end_progress。
每次调用您的一个上载过程时,就通过 #hook_dict 表将值传递给它。其中包括 '开始进度' 值和 '结束进度' 值。这些值定义所构建的上载应包括对远程数据库所做更改的时间段。在 '开始进度' 之前发生的操作已经上载。在 '结束进度' 之后发生的操作应在下一同步期间上载。
在实现脚本式上载时有一种常见错误,就是所创建的存储过程只能通过使用 sp_hook_dbmlsync_upload_end 或 sp_hook_dbmlsync_end 挂接判断上载是否成功应用于统一数据库。这种方法并不可靠。
例如,下面的示例尝试通过在每一行上使用一位来跟踪该行是否需要上载的方法处理插入。插入行时设置该位,成功提交上载后即在 sp_hook_dbmlsync_upload_end 挂接中清除该位。
// // DO NOT DO THIS! // CREATE TABLE t1 ( pk integer primary key, val varchar( 256 ), to_upload bit DEFAULT 1 ); CREATE PROCEDURE t1_ins() RESULT( pk integer, val varchar(256) ) BEGIN SELECT pk, val FROM t1 WHERE to_upload = 1; END; CREATE PROCEDURE sp_hook_dbmlsync_upload_end() BEGIN DECLARE upload_status varchar(256); SELECT value INTO upload_status FROM #hook_dict WHERE name = 'upload status'; if upload_status = 'committed' THEN UPDATE t1 SET to_upload = 0; END IF END; CREATE PUBLICATION p1 WITH SCRIPTED UPLOAD ( TABLE t1 USING ( PROCEDURE t1_ins FOR UPLOAD INSERT ) ); |
此方法在多数情况下有效。如果在发送上载后服务器确认上载前的这段时间,因为发生硬件或软件故障停止了 dbmlsync,此方法就会失败。在这种情况下,上载可应用于统一数据库,但不会调用 sp_hook_dbmlsync_upload_end 挂接,也不会清除 to_upload 位。因此,在下一同步中,将为已经上载的行上载插入。这通常会导致同步失败,因为这会在统一数据库上生成重复主键错误。
还有一种情况会出现问题,就是在发送上载后确认上载前的这段时间丢失与 MobiLink 服务器的通信。在这种情况下,dbmlsync 不知道上载是否已成功应用。Dbmlsync 调用 sp_hook_dbmlsync_upload_end 挂接并将上载状态设置为未知。写入挂接时,这会阻止清除 to_upload 位。如果服务器尚未应用上载,该操作就是正确的。但如果已应用上载,就会同样发生上一段所述的问题。在上述两种情况下,在手工干预解决问题之前,受影响的远程数据库将不能再次同步。
使用脚本式上载时,远程数据库中需要上载的数据可能会被从统一数据库中下载的数据覆盖。这会导致对远程数据库所做的更改丢失。如果您的上载过程构建的每个上载都包括在调用 sp_hook_dbmlsync_set_upload_end_progress 挂接之前在远程数据库中提交的所有更改,Dbmlsync 可防止这种数据丢失。
下面的示例显示违反此规则时将如何丢失数据:
时间 | |
---|---|
1:05:00 | 在统一数据库和远程数据库中均存在行 R。在远程数据库中,使用一些新值 R1 更新了行 R 并提交了更改。 |
1:06:00 | 在统一数据库中,将行 R 更新为一些新值 R2 并提交了更改。 |
1:07:00 | 发生同步。编写上载脚本,以便上载仅包含 1:00:00 之前提交的操作。这违反了我们的规则,因为这会阻止上载构建上载之前发生的所有操作。上载中不包括对行 R 的更改,因为此更改发生在 1:00:00 以后。从服务器收到的下载中包含行 R2。在远程数据库中应用下载时,行 R2 将替换行 R1。在远程数据库上的更新将丢失。 |
Dbmlsync 使用多种机制来确保下载不会覆盖在调用 sp_hook_dbmlsync_set_upload_end_progress 挂接时尚未提交,或在调用 sp_hook_dbmlsync_set_upload_end_progress 挂接以后提交的任何更改。
在调用此挂接之前提交的任何更改不受保护,并可能在应用下载时被覆盖。但只要更改包括在(在构建下载之前发送的)上载中,更改就会发送到 MobiLink 服务器,然后服务器端脚本就能够在构建下载之前使用统一数据库中的数据解决这个问题。
Copyright © 2009, iAnywhere Solutions, Inc. - SQL Anywhere 11.0.1 |