Files
BLD_sync/docs/incremental-sync.md
Misaka_Company bc461c12ce 📝 docs: add sync mechanism documentation with Mermaid diagrams
Add documentation for incremental sync (change-log driven polling,
per-PK verification, fault recovery) and full sync (batch transfer,
auto table creation, IDENTITY_INSERT handling).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 11:08:06 +08:00

6.7 KiB
Raw Blame History

增量同步机制 (run_incremental_sync.py)

概述

增量同步是一个长轮询服务,持续监控 SQL Server 中的 TableChangeLog 变更日志表,将 Access 数据源的变更实时同步到 SQL Server 目标表。

  • 入口脚本: run_incremental_sync.py
  • 计划任务: AutoRun-run_incremental_sync(用户登录时自动启动)
  • 轮询间隔: 30 秒(POLL_INTERVAL

整体架构

graph LR
    subgraph 数据源侧
        A[Access .accdb 文件] -->|VBA 宏写入日志| B[TableChangeLog]
    end

    subgraph 增量同步服务
        B -->|轮询 Synced=0| C[run_incremental_sync.py]
        C -->|读最新数据| A
        C -->|DELETE + INSERT| D[SQL Server 目标表]
        C -->|标记 Synced=1| B
    end

    subgraph 监控
        C -->|心跳| E[Uptime Kuma]
    end

变更日志驱动

Access 端的 VBA 宏在数据变更时,向 TableChangeLog 表写入一条记录:

字段 说明
LogID 自增主键
TableAddress Access 文件路径(多种格式)
TableName 发生变更的 Access 表名
RecordID 变更记录的主键值
Synced 同步标记0=未同步1=已同步

路径匹配策略

VBA 端写入的 TableAddress 有多种格式,系统构造 4 种候选值进行匹配:

;DATABASE=\\192.168.110.114\生产进度表\成品入库.accdb   ← 网络路径前缀
LOCAL=\\server\share\file.accdb                         ← 本地等号前缀
LOCAL:\\server\share\file.accdb                         ← 本地冒号前缀
\\192.168.110.114\生产进度表\成品入库.accdb              ← 裸路径

核心同步流程

flowchart TD
    START([服务启动]) --> POLL[轮询 TableChangeLog<br/>WHERE Synced=0]

    POLL -->|无记录| SLEEP[休眠 POLL_INTERVAL 秒]
    SLEEP --> POLL

    POLL -->|发现未同步记录| GROUP[按 TableName 分组<br/>合并同一表的 record_ids]

    GROUP --> CONNECT[连接 Access 数据库]

    CONNECT --> FOR_EACH{{遍历每个表}}

    FOR_EACH --> CHECK_TABLE{目标表是否存在?}

    CHECK_TABLE -->|不存在| AUTO_CREATE[自动建表<br/>ensure_schema + create_table_from_access]
    CHECK_TABLE -->|存在| READ_ACCESS
    AUTO_CREATE --> READ_ACCESS

    READ_ACCESS[A. 从 Access 读取最新数据<br/>SELECT WHERE PK IN ...]

    READ_ACCESS --> EXTRACT_PK[提取 inserted_pk_set<br/>Access 实际返回的主键集合]

    EXTRACT_PK --> DELETE[B. 删除目标表旧记录<br/>DELETE WHERE PK IN ...]

    DELETE --> HAS_NEW{{有新数据?}}

    HAS_NEW -->|有| IDENTITY_CHECK{含 IDENTITY 列?}
    IDENTITY_CHECK -->|是| ID_ON[SET IDENTITY_INSERT ON]
    IDENTITY_CHECK -->|否| INSERT
    ID_ON --> INSERT[C. 批量插入新记录<br/>executemany]
    INSERT --> ID_OFF[SET IDENTITY_INSERT OFF]
    ID_OFF --> VERIFY

    HAS_NEW -->|无| VERIFY

    VERIFY[D. 提交前逐主键校验] --> VERIFY_OK{校验通过?}

    VERIFY_OK -->|通过| MARK_SYNCED[E. 标记 Synced=1]
    MARK_SYNCED --> COMMIT[F. 提交事务]
    COMMIT --> POST_VERIFY

    POST_VERIFY{提交后复核<br/>ENABLE_POST_COMMIT_VERIFY} -->|关闭| SUCCESS
    POST_VERIFY -->|开启| POST_OK{复核通过?}
    POST_OK -->|通过| SUCCESS[记录同步成功日志]
    POST_OK -->|失败| REVERT[回滚 Synced=0<br/>下一轮重试]

    VERIFY_OK -->|失败| ROLLBACK[回滚事务<br/>保持 Synced=0]
    ROLLBACK --> NEXT_TABLE

    SUCCESS --> NEXT_TABLE
    REVERT --> NEXT_TABLE

    NEXT_TABLE{{下一张表?}} -->|是| FOR_EACH
    NEXT_TABLE -->|否| CLOSE_ACC[关闭 Access 连接]
    CLOSE_ACC --> SUMMARY[输出文件级汇总]
    SUMMARY --> POLL

    style VERIFY fill:#f9f,stroke:#333
    style POST_VERIFY fill:#f9f,stroke:#333
    style AUTO_CREATE fill:#bbf,stroke:#333

逐主键校验机制

传统的"数量对比"方法存在缺陷:少插和漏删的错误可能互相抵消。本系统采用逐主键确认方式:

flowchart LR
    subgraph 输入
        A[record_ids<br/>日志中的主键列表]
        B[inserted_pk_set<br/>Access 实际读到的主键]
    end

    subgraph 删除校验
        C[差集: record_ids - inserted_pk_set<br/>= 应删除的主键]
        D[查询目标表<br/>这些主键是否还存在?]
        C --> D
        D -->|还存在任一条| E[❌ 校验失败]
        D -->|全部不存在| F[✅ 删除校验通过]
    end

    subgraph 插入校验
        G[查询目标表<br/>inserted_pk_set 是否都在?]
        G -->|有任一条查不到| E
        G -->|全部存在| H[✅ 插入校验通过]
    end

    A --> C
    B --> C
    B --> G

校验函数说明

函数 作用
fetch_existing_pks() 分批查询目标表返回实际存在的主键集合IN 子句每批不超过 900 个参数)
verify_sync_result() 执行删除校验 + 插入校验,失败时抛出 SyncVerificationError
_norm_key() 主键归一化为字符串,规避 Access/SQL Server 类型差异
_chunked() 将列表分批,避免超出 SQL Server 参数上限

故障恢复

stateDiagram-v2
    [*] --> Pending: VBA 写入日志 Synced=0
    Pending --> Syncing: 同步服务读取
    Syncing --> Verified: 校验通过 + 提交
    Verified --> Committed: Synced=1 标记生效
    Committed --> [*]: 同步完成

    Syncing --> Rollback: 校验失败 / 异常
    Rollback --> Pending: 事务回滚 Synced=0<br/>下一轮自动重试

    Committed --> Pending: 提交后复核失败<br/>Synced 撤回为 0

三层保障

  1. 提交前校验 — 删/插完成后、标记 Synced=1 前,逐主键确认结果正确
  2. 事务回滚 — 校验失败或异常时回滚事务,保持 Synced=0,下一轮自动重试
  3. 提交后复核commit() 后再查一次数据库确认数据已持久化(可通过 ENABLE_POST_COMMIT_VERIFY 开关控制)

轮询策略

while True:
    has_work = process_sync_task()

    if has_work:
        sleep(0.1)      # 有积压,快速重试
    else:
        sleep(30)        # 无工作,标准间隔

有未处理数据时以 0.1 秒间隔快速处理积压;无数据时按 POLL_INTERVAL 休眠。

自动建表

当目标表在 SQL Server 中不存在时,系统根据 Access 表的列定义自动建表:

Access cursor.description → 类型映射 → CREATE TABLE 语句
Access 类型 SQL Server 类型
int INT(主键时追加 IDENTITY(1,1) PRIMARY KEY
float FLOAT
bool BIT
datetime.datetime DATETIME
decimal.Decimal DECIMAL(p, s)
str (size ≤ 4000) NVARCHAR(size)
str (size > 4000) NVARCHAR(MAX)