# 增量同步机制 (run_incremental_sync.py) ## 概述 增量同步是一个**长轮询服务**,持续监控 SQL Server 中的 `TableChangeLog` 变更日志表,将 Access 数据源的变更实时同步到 SQL Server 目标表。 - **入口脚本**: `run_incremental_sync.py` - **计划任务**: `AutoRun-run_incremental_sync`(用户登录时自动启动) - **轮询间隔**: 30 秒(`POLL_INTERVAL`) ## 整体架构 ```mermaid 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 ← 裸路径 ``` ## 核心同步流程 ```mermaid flowchart TD START([服务启动]) --> POLL[轮询 TableChangeLog
WHERE Synced=0] POLL -->|无记录| SLEEP[休眠 POLL_INTERVAL 秒] SLEEP --> POLL POLL -->|发现未同步记录| GROUP[按 TableName 分组
合并同一表的 record_ids] GROUP --> CONNECT[连接 Access 数据库] CONNECT --> FOR_EACH{{遍历每个表}} FOR_EACH --> CHECK_TABLE{目标表是否存在?} CHECK_TABLE -->|不存在| AUTO_CREATE[自动建表
ensure_schema + create_table_from_access] CHECK_TABLE -->|存在| READ_ACCESS AUTO_CREATE --> READ_ACCESS READ_ACCESS[A. 从 Access 读取最新数据
SELECT WHERE PK IN ...] READ_ACCESS --> EXTRACT_PK[提取 inserted_pk_set
Access 实际返回的主键集合] EXTRACT_PK --> DELETE[B. 删除目标表旧记录
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. 批量插入新记录
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{提交后复核
ENABLE_POST_COMMIT_VERIFY} -->|关闭| SUCCESS POST_VERIFY -->|开启| POST_OK{复核通过?} POST_OK -->|通过| SUCCESS[记录同步成功日志] POST_OK -->|失败| REVERT[回滚 Synced=0
下一轮重试] VERIFY_OK -->|失败| ROLLBACK[回滚事务
保持 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 ``` ## 逐主键校验机制 传统的"数量对比"方法存在缺陷:少插和漏删的错误可能互相抵消。本系统采用**逐主键确认**方式: ```mermaid flowchart LR subgraph 输入 A[record_ids
日志中的主键列表] B[inserted_pk_set
Access 实际读到的主键] end subgraph 删除校验 C[差集: record_ids - inserted_pk_set
= 应删除的主键] D[查询目标表
这些主键是否还存在?] C --> D D -->|还存在任一条| E[❌ 校验失败] D -->|全部不存在| F[✅ 删除校验通过] end subgraph 插入校验 G[查询目标表
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 参数上限 | ## 故障恢复 ```mermaid stateDiagram-v2 [*] --> Pending: VBA 写入日志 Synced=0 Pending --> Syncing: 同步服务读取 Syncing --> Verified: 校验通过 + 提交 Verified --> Committed: Synced=1 标记生效 Committed --> [*]: 同步完成 Syncing --> Rollback: 校验失败 / 异常 Rollback --> Pending: 事务回滚 Synced=0
下一轮自动重试 Committed --> Pending: 提交后复核失败
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)` |