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

211 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 增量同步机制 (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<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
```
## 逐主键校验机制
传统的"数量对比"方法存在缺陷:少插和漏删的错误可能互相抵消。本系统采用**逐主键确认**方式:
```mermaid
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 参数上限 |
## 故障恢复
```mermaid
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)` |