Click here to view and discuss this page in DocCommentXchange. In the future, you will be sent there automatically.

SQL Anywhere 11.0.1 (中文) » MobiLink - 客户端管理 » 用于 MobiLink 的 SQL Anywhere 客户端 » 脚本式上载 » 脚本式上载的设计注意事项

 

没有表锁定的脚本式上载

缺省情况下,dbmlsync 在调用任何上载脚本之前锁定正在同步的表并一直维护这些锁到提交下载。通过将扩展选项 LockTables 设置为 off,可防止表锁定。

建议尽可能使用缺省表锁定行为。进行没有表锁定的脚本式上载会显著增加必须考虑的问题,以及创建正确可行解决方案的难度。这种操作应仅由非常了解数据库并发和同步概念的高级用户来尝试。

使用没有表锁定的隔离级别

禁用表锁定时,运行上载存储过程的隔离级别非常重要,因为它将决定处理未提交事务的方式。启用表锁定时,这不是问题,因为表锁定可确保构建上载时已同步表上没有未提交更改。

上载存储过程将在数据库用户(在 dbmlsync 命令行上指定)的缺省隔离级别运行,除非在上载存储过程中明确更改隔离级别。

隔离级别 0 是数据库的缺省隔离级别,但在使用没有表锁定的脚本式上载时,建议您不要在隔离级别 0 运行上载过程。如果实现没有表锁定的脚本式上载并且使用隔离级别 0,则可能上载未提交的更改,这可能导致以下问题:

  • 未提交更改可能被回退,从而导致将不正确的数据发送到统一数据库。

  • 未提交事务可能不完整,在这种情况下,可能仅上载了部分事务,从而导致统一数据库处于不一致状态。

可以改用隔离级别 1、2、3 或快照。所有这些隔离级别均可确保不上载未提交的事务。

使用隔离级别 1、2 或 3 时,如果表中有未提交更改,则会导致上载存储过程阻塞。因为是在 dbmlsync 连接到 MobiLink 服务器时调用上载存储过程,所以这会阻碍服务器连接。如果使用隔离级别 1,通过在 select 语句中使用 READPAST table-hint 子句,能够避免阻塞。

快照隔离是个好选择,因为它可同时防止阻塞和读取未提交更改。

丢失未提交更改

如果选择放弃表锁定,则必须有在发生同步时处理未提交操作的机制。若要理解其中的原因,请看下面的示例。

假设表正由脚本式上载进行同步。为简单起见,假设仅在上载插入。表包含 insert_time 列,该列是指示每行的插入时间的时间戳。

通过选择表中 insert_time 在上次成功上载之后和开始构建当前上载(即调用 sp_hook_dbmlsync_set_upload_end_progress 挂接的时刻)之前的所有已提交行,构建每个上载。假设发生了以下操作。

时间
1:00:00 成功进行同步。
1:04:00 行 R 插入到表中但未提交。R 的 insert_time 列设置为 1:04:00。
1:05:00 发生同步。上载插入时间介于 1:00:00 和 1:05:00 之间的行。未上载行 R,因为它未提交。同步进度设置为 1:05:00。
1:07:00 提交在 1:04:00 插入的行。R 的 insert_time 列继续包含 1:04:00。
1:10:00 发生同步。上载插入时间介于 1:05:00 和 1:10:00 之间的行。不上载行 R,因为它的 insert_time 不在范围内。事实上,从未上载行 R。

总而言之,在同步之前发生却在同步之后提交的所有操作都容易这样丢失。

处理未提交的事务

处理未提交事务有一种最简单的方法,就是使用 sp_hook_dbmlsync_set_upload_end_progress 挂接将每个同步的结束进度设置为调用此挂接时的最早未提交事务的开始时间。可使用 sa_transactions 系统过程确定此时间,如下所示:

SELECT min( start_time )
FROM sa_transactions()

在这种情况下,上载存储过程必须忽略在 sp_hook_dbmlsync_set_upload_end_progress 挂接中使用 sa_transactions 计算并使用 #hook_dict 表传递的结束进度。存储过程应只上载开始进度之后发生的所有已提交操作。这可确保下载不覆盖含有仍需上载的更改的行。还可确保即使有未提交事务也会及时上载操作。

此解决方案可确保不丢失任何操作,但某些操作可能会上载多次。必须将服务器端脚本编写为能够处理多次上载的操作。下面的示例显示了此设置中如何多次上载一行。

时间
1:00:00 成功进行同步。
2:00:00 插入行 R1 但未提交。
2:10:00 插入行 R2 并提交。
3:00:00 发生同步。上载在 1:00 和 3:00 之间发生的操作。上载行 R2 并将进度设置为 2:00,因为它是最早未提交事务的开始时间。
4:00:00 提交行 R1。
5:00:00 发生同步。上载在 2:00 和 5:00 之间发生的操作并将进度设置为 5:00。上载包含行 R1 和 R2,因为这两行的时间戳都在上载范围内。这样,R2 就上载了两次。

如果统一数据库是 SQL Anywhere,则可以通过在统一数据库的 upload_insert 脚本中使用 INSERT ...ON EXISTING UPDATE 语句处理冗余上载的插入操作。

对于其它统一数据库,可在 upload_insert 脚本调用的存储过程中采用类似的逻辑。只需编写一个检查,检查统一数据库中是否已经存在主键与插入行主键相同的行。如果该行存在,则将其更新;否则插入新行。

服务器端有冲突检测或解决逻辑时,冗余上载的删除和更新操作就成了问题。如果在服务器端编写冲突检测和解决脚本,则这些脚本必须能够处理冗余上载。

如果统一数据库会重复使用主键值,冗余上载的删除就是个大问题。请看下面的事件序列:

  1. 将主键为 100 的行 R 插入远程数据库并上载到统一数据库。

  2. 在远程数据库上删除行 R 并上载删除操作。

  3. 将主键为 100 的新行 R' 插入统一数据库。

  4. 再次从远程数据库上载第 2 步中删除行 R 的删除操作。这样很容易导致从统一数据库不恰当地删除 R'。

另请参见