1. 为什么需要关心SQLite的数据同步?

在小李开发健身记录APP的过程中,他发现用户经常抱怨手机和智能手表的数据不同步:手机记录的3000步在平板上显示成2800步,体重数据偶尔会错乱。这种多设备间的数据不一致问题直接影响用户体验,甚至导致用户流失。

传统SQLite的本地存储特性决定了它没有内置的同步机制。当多个设备独立操作数据库时,就像多个厨师在同一个厨房分别改菜谱,最后端上桌的菜品必然混乱。我们需要一套机制让所有设备像交响乐团一样协同演奏。

2. 基于版本控制的增量同步方案

2.1 数据库结构设计

-- 使用SQLite 3.32.0版本
-- 用户主表增加元数据字段
CREATE TABLE user_data (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    device_id TEXT NOT NULL,     -- 生成设备指纹(如UUID)
    record_time DATETIME,       -- 数据记录时间
    steps INTEGER,              -- 步数
    weight REAL,                -- 体重(公斤)
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    version INTEGER DEFAULT 0   -- 乐观锁版本号
);

-- 版本变更记录表
CREATE TABLE sync_log (
    log_id INTEGER PRIMARY KEY AUTOINCREMENT,
    table_name TEXT NOT NULL,   -- 变更表名
    record_id INTEGER NOT NULL,  -- 记录ID
    operation_type TEXT CHECK(operation_type IN ('INSERT', 'UPDATE', 'DELETE')),
    old_data TEXT,              -- JSON格式旧数据
    new_data TEXT,              -- JSON格式新数据
    device_id TEXT NOT NULL,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);

2.2 数据变更捕获

通过触发器记录数据变动:

-- 更新触发器
CREATE TRIGGER user_data_update_trigger 
AFTER UPDATE ON user_data 
FOR EACH ROW
BEGIN
    INSERT INTO sync_log(table_name, record_id, operation_type, 
        old_data, new_data, device_id)
    VALUES (
        'user_data', 
        OLD.id,
        'UPDATE',
        json_object(
            'steps', OLD.steps,
            'weight', OLD.weight,
            'version', OLD.version
        ),
        json_object(
            'steps', NEW.steps,
            'weight', NEW.weight,
            'version', NEW.version
        ),
        NEW.device_id
    );
    
    UPDATE user_data SET version = version + 1 WHERE id = NEW.id;
END;

3. 同步服务核心逻辑实现

使用Python 3.9 + Flask构建同步服务,技术栈保持统一:

# sync_api.py
from flask import Flask, jsonify, request
import sqlite3
import json
from datetime import datetime

app = Flask(__name__)

def get_db_connection():
    conn = sqlite3.connect('/data/fitness.db')
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/sync', methods=['POST'])
def handle_sync():
    """
    同步请求示例:
    {
        "device_id": "android_123456",
        "last_sync_time": "2024-03-20 14:30:00",
        "pending_changes": [
            {
                "table": "user_data",
                "record_id": 42,
                "new_version": 3,
                "data": {"steps": 5000}
            }
        ]
    }
    """
    sync_data = request.json
    conn = get_db_connection()
    
    # 处理客户端变更
    for change in sync_data['pending_changes']:
        # 使用乐观锁校验
        cursor = conn.execute(
            'SELECT version FROM user_data WHERE id = ?',
            (change['record_id'],)
        )
        current_version = cursor.fetchone()['version']
        
        if current_version + 1 == change['new_version']:
            conn.execute(
                '''UPDATE user_data 
                   SET steps=?, version=version+1, updated_at=?
                   WHERE id=?''',
                (change['data']['steps'], datetime.now(), change['record_id'])
            )
    
    # 返回服务端变更
    server_changes = conn.execute(
        '''SELECT * FROM sync_log 
           WHERE timestamp > ? AND device_id != ?''',
        (sync_data['last_sync_time'], sync_data['device_id'])
    ).fetchall()
    
    return jsonify({
        'new_changes': [dict(row) for row in server_changes],
        'current_time': datetime.now().isoformat()
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

4. 典型应用场景分析

4.1 健身数据多端同步

当用户使用手机记录跑步数据后,智能手表需要立即显示最新数据。通过版本控制系统,手表在同步时会获取手机产生的变更日志,同时上传本地记录的静息心率数据。

4.2 医疗设备数据汇总

多台病房监护仪使用SQLite存储生命体征数据,中心服务器定时拉取各设备数据。通过冲突合并策略,即使不同设备对同一患者记录有微小时间差的血压数据,也能正确保留所有关键变化。

5. 技术方案的优劣对比

5.1 优势亮点

  • 网络容忍性:设备断网时可继续记录数据,恢复联网后自动同步
  • 精确溯源:通过sync_log表完整记录数据变化时间线
  • 增量传输:仅同步变化部分数据,节省流量消耗
  • 版本回滚:可根据版本号恢复到任意历史状态

5.2 潜在缺陷

  • 合并冲突需要定制策略
  • 长期运行后日志表体积膨胀
  • 需要自行处理设备时钟不同步问题

6. 实战开发注意事项

  1. 设备标识管理:推荐使用加密的UUID作为设备指纹,避免用户篡改
  2. 日志清理机制:添加定期归档策略,例如只保留最近30天的变更记录
  3. 数据加密传输:建议使用TLS 1.3加密同步通道
  4. 性能优化:为sync_log表建立复合索引(timestamp + device_id)
  5. 测试边界条件
    # test_sync.py
    def test_concurrent_update():
        # 模拟两个设备同时修改同一条记录
        deviceA = {'id': 1, 'version': 2}
        deviceB = {'id': 1, 'version': 2}
    
        # 设备A先提交更新
        server.update(deviceA)
    
        # 设备B携带过期版本号提交
        result = server.update(deviceB)
        assert 'version_conflict' in result
    

7. 架构演进方向

对于大型应用,可考虑以下优化路径:

  1. 将SQLite替换为支持CRDT(无冲突复制数据类型)的数据库
  2. 引入消息队列解耦同步服务
  3. 增加二进制差异压缩算法减少数据传输量
  4. 实现自动冲突解决策略(如时间优先、设备优先级等)