开启分表(窄表)
窄表模式是在分表基础上更进一步的存储形态:数据不仅存到独立的物理表,而且每个字段对应一个独立的列(列名即字段的 normalized_storage_field),不再使用 valueN / labelN 预留列。相比宽表,窄表的表结构更贴近传统数据库设计,便于直接用 SQL 查询、建索引、对接 BI 工具。
三种存储模式对比
CETA 表单数据有三种物理存储形态,由 form_entity 上的三个标志位组合决定:
| 模式 | 开关组合 | 物理表 | 业务列结构 |
|---|---|---|---|
| A. 全局宽表(默认/旧) | standalone=0 | 全局共享 form_entity_data | value1..valueN / label1..labelN(多表单共用预留列) |
| B. 分表宽表 | standalone=1,standalone_table_normalized=0 | 独立表 <standalone_table_name> | 仍是 valueN / labelN(只是物理表独立了) |
| C. 窄表(本节目标) | standalone=1,standalone_table_normalized=1,standalone_table_created=1 | 独立表 <standalone_table_name> | 每字段一列,列名 = normalized_storage_field |
关于这些标志位(重要):
standalone_table_normalized和standalone_table_created都由 CETA 后端在开启窄表 / 建表成功后自动置 1,不需要也不应该手动改 SQL:
- 开启窄表时后端调用
enableFormEntityDataStandaloneTableNormalized(...),把standalone_table_normalized置 1,并自动为每个字段回填normalized_storage_field。- 建表方法
createOrUpdateTable(...)执行成功后,后端调用updateFormEntityStandaloneTableCreated(id, 1)把standalone_table_created置 1。也就是说,你只要触发「开启窄表 + 建表」的动作,三个标志位会由后端负责设置到位。本文中出现的
UPDATE form_entity SET ...SQL 仅用于排查/回滚等特殊场景,正常流程无需手动执行。
字段与列的映射关系:
- 宽表列(A、B 通用):
form_entity_field.storage_field(如value43);label 列 = value 列名对应的labelN(value43→label43)。 - 窄表列(C):
form_entity_field.normalized_storage_field(如applicant_name);label 列 = value 列名 +_label(applicant_name→applicant_name_label)。 - 系统列三种模式同名:
id/form_entity_id/deleted/created_time/updated_time/user_id/updated_user_id/flow_instance_id/thread_id/node_id/object_version。
有
_label列(value + label 两列)的字段类型:SELECT、ACL、CASCADER、TREE_SELECT,以及DATE。其余字段类型只有单个 value 列,LOOKUP字段无存储列(跳过)。
开启步骤
-
后端配置:确保配置文件中
form_entity_data.standalone.enable = true(与宽表分表相同的前置开关)。 -
项目 + 表单设置:在项目上允许独立数据表,并在目标表单上设置独立表名(
standalone_table_name)。 -
开启窄表 + 建表(后端自动置标志位):触发平台的「开启窄表」动作,后端会:
- 调用
enableFormEntityDataStandaloneTableNormalized(...),把standalone_table_normalized置 1,并为每个字段回填normalized_storage_field; - 调用
FormEntityDataStandaloneTableNormalizedService.createOrUpdateTable(formEntity)生成窄表,建表成功后自动把standalone_table_created置 1。

触发方式:
-
通过表单设计器把该表单标记为 standalone + normalized 并保存(平台会自动完成置标志位 + 建表);
-
或在目标环境用一次性调用触发上述流程(需
form_entity_data.standalone.enable=true)。
⚠️ 窄表结构必须由平台建表逻辑生成,不要手写 DDL,也不要手动
UPDATE这些标志位。因为列名(normalized_storage_field)、label 列、类型映射、以及standalone_table_normalized/standalone_table_created标志都由后端统一负责。建表是幂等的:表已存在则只补缺列,不会重建。 - 调用
-
建表产物确认:
- 库中存在表
<standalone_table_name>,含id+ 全部系统列 + 每字段业务列。 - 执行
SELECT token, storage_field, normalized_storage_field, type FROM form_entity_field WHERE form_entity_id = <feid>,确认normalized_storage_field均非空。

- 库中存在表
长文本字段的存储差异(重要)
长文本类字段(TEXTAREA / RICH_TEXT / LONG_TEXT)在两种模式下存储位置不同:
| 模式 | 长文本主值存哪 |
|---|---|
| 宽表(A/B) | 子表 form_entity_data_text(long_text 列,locale IS NULL 为主值),宽表的 valueN 列此时通常为空 |
| 窄表(C) | 内联在窄表列(normalized_storage_field,类型 TEXT) |
这一差异直接影响数据迁移:主迁移只搬 valueN 列,长文本必须单独回填。详见下一节。
如果表单已有数据
如果表单在切换到窄表前已经积累了宽表数据(模式 A 或 B),需要把这些历史数据迁移到窄表,才能无损切换。完整的迁移流程见:怎么迁移宽表数据到窄表。
开启之后:日常演进(加字段 / 删字段 / 删表)
窄表建好后,表单还会持续演进。加字段由后端自动加列;但删字段、删表属于破坏性操作,后端不会自动执行,需 DBA 手动清理。完整的生命周期说明见:窄表的全生命周期(加字段 / 删字段 / 加表 / 删表)。