上篇讲的多设备协同表模式有个硬限制------对端数据只读。但很多场景需要多端都能改同一份数据:手机和平板都能编辑同一份文档,两台设备都能改同一份购物车。这时就要用单版本表模式,配合 schema 文件指定同步列和解冲突列。下面用一个"多端共编文档"案例,把单版本表模式和 schema 配置的众多约束讲清楚。
一、案例背景:多端共编文档
一个笔记应用,用户在手机和平板上都能编辑同一份文档。需求:
- 两端都能修改文档内容
- 一端修改后,另一端同步看到
- 同一文档在两端同时修改时,要有冲突解决机制
多设备协同表模式搞不定(对端数据只读),必须用单版本表模式。
二、单版本表模式的运作机制
和多设备协同表的关键区别:同步数据直接写入本地表,而不是隔离存储在分布式表里。
这意味着:
- 所有设备的数据在同一个表里
- 任何设备都能修改任何数据(包括对端同步过来的)
- 需要配置 schema 指定同步列 和解冲突列来处理冲突
解冲突列是核心概念:当两端同时修改同一条数据时,系统按解冲突列的值判断"这是不是同一条数据",然后用后到的覆盖先到的(或按其他策略)。解冲突列通常是全局唯一字段(如 UUID)。
三、第一步:设置单版本表模式
typescript
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
const DOMAIN = 0x0000;
let store: relationalStore.RdbStore | undefined = undefined;
async function initSingleVersionStore(context: common.UIAbilityContext) {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S3
};
// 单版本表模式的配置
const DISTRIBUTED_CONFIG: relationalStore.DistributedConfig = {
autoSync: false,
asyncDownloadAsset: false,
enableCloud: false,
tableType: relationalStore.DistributedTableType.SINGLE_VERSION // 关键:单版本类型
};
relationalStore.getRdbStore(context, STORE_CONFIG).then(async (rdbStore: relationalStore.RdbStore) => {
store = rdbStore;
// 建表:NAME 设为 UNIQUE,作为解冲突列
await store.executeSql(
'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL UNIQUE, AGE INTEGER, SALARY REAL, CODES BLOB)'
);
// 设置分布式表,指定单版本类型
await store.setDistributedTables(
['EMPLOYEE'],
relationalStore.DistributedType.DISTRIBUTED_DEVICE,
DISTRIBUTED_CONFIG
);
}).catch((err: BusinessError) => {
hilog.error(DOMAIN, 'sync', `初始化失败: ${err.message}`);
});
}
四、第二步:配置 schema 文件(关键且约束多)
单版本表模式必须配置 schema 文件,指定同步列和解冲突列。schema 文件的位置和名称是固定的,不能自定义:
6
- 文件名 :
sync_schema.json - 文件路径 :
../entry/src/main/resources/rawfile/arkdata/schema/sync_schema.json
路径(路径不对会读取不到,设置分布式表会失败
4.1 schema 文件结构
json
{
"dbSchema": [
{
"version": 0,
"bundleName": "com.example.docSync",
; "dbName": "RdbTest",
"tables": [
{
"tableName": "EMPLOYEE",
"deviceSyncFields": ["NAME", "AGE", "SALARY", "CODES"],
"cloudType": ["Local"],
"fields": [
{
"columnName": "ID",
"type": "Integer",
"primaryKey": false,
"notNull": false,
"autoIncrement>Increment": true
},
{
"columnName": "NAME",
"type": "Text",
"primaryKey": true,
"notNull": true,
"autoIncrement": false
},
{
"columnName": "AGE",
"type": "Integer",
"primaryKey": false,
"notNull": false,
"autoIncrement": false
},
{
"columnName": "SALARY",
"type": "Float",
"primaryKey": false,
"notNull": false,
"autoIncrement": false
},
{
"columnName": "CODES",
"type": "Blob",
"primaryKey": false,
"notNull": false,
"autoIncrement": false
}
]
}
]
}
]
}
4.2 关键字段解读
|G- deviceSyncFields:指定哪些列参与同步。未列出的列不会同步。
fields里每个字段的primaryKey: true:表示这是解冲突列(注意:这里的 primaryKey 不是数据库主键,而是解冲突标识)autoIncrement:是否自增,要和表结构一致type:字段类型,可选Text/Integer/Long/Float/Double/BlobcloudType:表类型,Local表示本端表(API 12 起必填,API 26 起可选默认 Local)
本例中 NAME 设为解冲突列(primaryKey: true),它必须是有 UNIQUE 约束的全局唯一字段(如 UUID)。
五、schema 配置的众多约束(容易出错的地方)
官方列了一大堆 schema 约束,这些都是容易踩的坑,逐条过一遍:
5.1 解冲突列不能变
schema 版本升级后,解冲突列不能从 "NAME" 改成 "AGE"。解冲突列是数据身份)份的依据,改了就乱套。
5.2 解冲突列只能有一个
不能同时指定 NAME 和 AGE 两个解冲突列。
5.3 同步列必须存在表中
schema 里写的字段名要和建D建表语句完全一致,大小写敏感。"NAMe" 和 "NAME" 是两个不同的字段。
5.4 同步列只能新增不能减少
版本升级时,`G同步列只能加不能减。减了会导致存量数据同步异常。
5.5 schema 变化时 version 必须增加
改了 schema 内容,version 要 +1,不然系统不会重新加载。
5.6 自增主键不能同步
自增主键7是本设备自增的,跨设备同步会冲突。$自增表必须配置一个非主键列做解冲突。
5.7 解冲突列必须有 UNIQUE 属性
建表时解冲突列要加 UNIQUE 约束,否则设置分布式表失败。
5.8 解冲突列不能有 null 值
存量数据有 null 会设置失败,增量数据写 null 会写入失败。
5.9 无主键表不支持单版本模式
表必须有主键才能用单版本表模式。
5.10 非自增主键必须同步且必须做解冲突列
如果主键不是自增的,那主键必须同步,且解冲突列必须是主键。
5.11 所有 UNIQUEDUNIQUE 列必须同步
表里所有加了 UNIQUE 的列,都要在 deviceSyncFields 里列出来。
5.12 not null 字段要有默认值或指定同步
NOT NULL 字段如果没有默认值,必须在同步列里,否则设置失败。
六、后向兼容策略(API 26 新增)
API 26.0.0 起,schema 新增 *backwardCompatiblePolicies` 字段,允许两端约束不一致而不导致同步失败:
json
{
"backwardCompatiblePolicies": [
{
"tableName": "EMPLOYEE",
"fieldsPolicy": [
{
"columnName": "HIGH",
"compatibleConstraints": [
{ "notNull": false, "hasDefault": true },
{ "notNull": true, "hasDefault": true }
]
}
]
}
]
}
这个策略适用于:两端同一个字段,一端 NOT NULL 另一端7端可以为空,但都有默认值。配了策略就能放行,不配会同步失败。
七、单版本表模式的同步操作
配好 schema 后,同步操作和多设备协同表模式一样:
typescript
// 推送本地变更
async function pushChanges() {
if (!store) return;
// 修改文档
await store.update({ SALARY: 20000 },
new relationalStore.RdbPredicates('EMPLOYEE').equalTo('NAME', 'doc_001'));
// 推送到其他设备
const deviceManager = distributedDeviceManager.createDeviceManager('com.example.docSync');
const deviceList = deviceManager.getAvailableDeviceListSync();
const syncTarget = deviceList.filter(i=> i.networkId).map(i => i.networkId);
const predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.inDevices(syncTarget);
const result = await store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates);
// 单版本表模式下,对端会直接更新本地表里的同一条数据(按解冲突列匹配)
}
八、两种模式对比总结
| 维度 | 多设备协同表 | 单版本表 |
|---|---|---|
| 数据存储 | 各设备隔离存储 | 直接写入本地表 |
| 修改对端数据 | 不支持 | 支持 |
| schema 配置 | 不支持/不需要 | 必须配置 |
| 冲突处理 | 无(数据隔离) | 按解冲突列覆盖 |
| 复杂度 | 低 | 高(约束多) |
| 适用场景 | 单向同步、只读展示 | 多端共编、双向修改 |
九、小小建议
- 数据只在一端产生,其他端只看:多设备协同表,简单
- **!多端都要改同一份数据:单版本表,但要$但要把 schema 约束逐条对照
- 不确定用哪个:先试多设备协同表,遇到"需要改对端数据"再迁单版本
迁移要趁早------同一张表不能在两种模式间切换,迁就得改表结构重建。
十、总结一下下
- schema 文件位置固定 :
rawfile/arkdata/schema/sync_schema.json,名和路径都不能改 - 解冲突列是核心:必须 UNIQUE、不能 null、只能一个、不能变
- 自增主键不能同步:自增表要配非主键解冲突列
- 同步列只能增不能减:版本升级时只能加列不能减列
- schema 改了 version 要+1:不然不重新加载
- 约束多要逐条对照:这一步偷懒必出问题
下一篇讲批量数据写入和查询性能优化,处理"几万条数据要快速入库""查询不能卡 UI"这类性能问题。