status: applied-with-db-retry-pending created: 2026-07-22 complexity: 🔴复杂
Forge 部分逻辑删除表使用可见生成列 logic_delete_active,通过 CASE WHEN del_flag = 0 THEN 1 ELSE NULL END 配合唯一索引,只约束未删除记录。第三方数据库工具导出数据时可能把该生成列及计算值写入 INSERT,MySQL 会以“生成列不能显式赋值”拒绝导入。
本变更目标:
logic_delete_active 生成列;del_flag 删除标记和普通复合唯一索引表达“仅未删除记录唯一”;del_flag 是逻辑删除必需字段,活跃唯一表达式仅在业务唯一键需要删除后重建时使用;AGENTS.md#5.11:当前推荐通过可见生成列 logic_delete_active 解决逻辑删除后的唯一键语义。forge-server/db/migration/V1.0.2__fix_existing_logic_delete_contract.sql 至 V1.0.47__add_outbound_security_policy.sql:累计为 55 张表建立可见生成列,并由 64 个唯一索引引用。@TableName 实体显式声明 @TableLogic;其中 26 张表没有生成列,证明生成列不是逻辑删除的必要组成。现有唯一键模型:
logic_delete_active tinyint
GENERATED ALWAYS AS (CASE WHEN del_flag = '0' THEN 1 ELSE NULL END) STORED,
UNIQUE KEY uk_xxx_active (tenant_id, business_code, logic_delete_active)
未删除记录得到索引值 1,同一业务键只能存在一条;已删除记录得到 NULL,利用 MySQL 唯一索引允许多个 NULL 的规则保留多条历史记录。
目标模型只保留一个删除字段:
del_flag bigint NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0正常,删除后写主键',
UNIQUE KEY uk_xxx_active (tenant_id, business_code, del_flag)
未删除记录统一使用 0,因此普通唯一索引只允许一个活跃业务键;删除时把 del_flag 更新为该行主键,每条历史记录得到不同标记,可以保留任意多条同业务键历史。该模型不依赖生成列、函数索引、部分索引或 NULL 唯一语义。
del_flag/deleted:负责逻辑删除状态、ORM 删除改写与查询过滤,是逻辑删除表的必要字段。logic_delete_active:不负责删除,只服务于部分业务唯一键;不是每张逻辑删除表都需要。information_schema 连接因当前账号来源限制返回 MySQL 1045;仓库 Flyway 与实体映射作为本轮静态权威基线,真实库校验列入部署验收。完整清单见 schema-audit.md。
del_flag 并删除可见生成列。information_schema.COLUMNS,已迁移或不存在的表安全跳过。@TableLogic 与自定义删除 SQL,保证删除时写入主键而不是常量 1。del_flag、deleted 或兼容的历史软删除字段。del_flag,不需要扩展删除标记语义。 del_flag bigint NOT NULL DEFAULT 0,
UNIQUE KEY uk_xxx_active (tenant_id, business_code, del_flag)
1;必须写当前行主键或同等唯一且稳定的删除令牌,否则同一业务键只能保留一条已删除记录。del_flag 使用 BIGINT/Long,实体使用 @TableLogic(value = "0", delval = "主键数据库列名")。sys_flow_node_config 使用字符串删除标记和自定义 Mapper 原子更新 SET del_flag = id,禁止调用通用 MP 删除方法。| 操作 | 表名 | 字段/索引 | 说明 |
|---|---|---|---|
| 修改 | 54 张数值主键表 | del_flag |
改为 BIGINT,存量已删除行回填自身主键 |
| 修改 | sys_flow_node_config |
del_flag |
改为 varchar(64),存量已删除行回填字符串主键 |
| 修改 | 55 张历史表 | 64 个 *_active 唯一索引 |
将末尾 logic_delete_active 键改为 del_flag |
| 删除 | 55 张历史表 | logic_delete_active |
删除可见生成列,消除 SQL 导入显式赋值问题 |
迁移只重写已删除行的 del_flag 墓碑值,不修改有效业务数据、租户数据或审计字段。
无 HTTP 路径、字段名或请求协议变更。54 张数值主键实体的 delFlag 类型改为 Long 并显式配置 MyBatis-Plus 删除值;EmployeeDTO 与 EmployeeQuery 的同名字段同步改为 Long;已有自定义删除 Mapper 改为写入主键;sys_flow_node_config 的两个删除入口改为专用 Mapper。
forge-server/db/migration/:新增版本化迁移。forge-server/forge-admin-server:新增跨模块迁移契约测试。AGENTS.md、Forge CRUD Skill 和编码规范:更新未来建表约定。ALTER TABLE,需评估大表 DDL 时间并在执行前备份。ALTER TABLE 可能持有元数据锁;生产执行前需检查长事务并安排维护窗口。information_schema.STATISTICS 中实际存在的索引动态生成 DROP INDEX 子句,不能假设历史索引名仍存在。logic_delete_active 列名时,在新结构上会报未知列;旧脚本需重新导出或清理字段后再导入。若必须回滚数据库结构,应按表先删除 del_flag 唯一索引,再恢复生成列和原唯一索引;将非零墓碑压回 1 前必须确认没有多条同业务键已删除历史,否则数据会违反旧结构或丢失区分能力。
del_flag,且每表有 information_schema 防重复保护。del_flag=主键列,查询条件保持 del_flag=0。${...} 占位符、无新增 logic_delete_active GENERATED ALWAYS 模式、SQL 中无隐式数据插入。information_schema.COLUMNS 应无 logic_delete_active;64 个唯一索引末位应为 del_flag;验证未删除重复插入失败、删除后 del_flag=主键、同值重建成功、再次删除成功以及数据导出再导入成功。test-spec.md。del_flag:是,但活跃唯一表的删除值必须由常量 1 升级为唯一主键墓碑。| 决策 | 选择 | 放弃方案 | 原因 |
|---|---|---|---|
| 活跃唯一表达 | 普通唯一索引末位使用 del_flag |
可见生成列、函数索引 | 只使用常规列和索引,数据库迁移兼容性更好 |
| 删除状态 | 0 表示有效,删除后写主键 |
固定写 1 |
同一字段同时承担状态和墓碑唯一标记,可保留多条已删除历史 |
| 迁移策略 | 新增 V1.0.51,按真实存在的旧索引原子替换索引和列 | 修改旧迁移、无条件删除历史索引 | 遵守 Flyway 历史不可变规则,并兼容索引重命名、缺失和部分执行状态 |
| ORM 集成 | 数值主键使用 @TableLogic(delval="主键列"),字符串主键用专用 Mapper |
Service 先查后改 | 保留 MP 查询过滤并确保删除更新原子化 |
| 普适规则 | 按唯一性需求判断 | 所有逻辑删除表机械改成 BIGINT | 避免无业务唯一键的表承担无价值改造 |
| Task | 状态 | 实际改动文件 | 备注 |
|---|---|---|---|
| 扫描与设计 | done | spec.md, schema-audit.md |
实体 81 张,生成列 55 张,活跃唯一索引 64 个 |
| 迁移与代码对齐 | done | V1.0.51__replace_logic_delete_generated_columns.sql、54 张数值主键实体、Flow 节点专用删除 Mapper、10 处自定义删除 SQL、代码生成器模板 |
数值主键删除由 MP 写入主键;字符串主键由 Mapper 写入主键 |
| 测试与编译 | done | LogicDeleteUniqueIndexPortabilityMigrationTest.java, test-spec.md, execution-log.md |
5 个契约测试通过,43 个聚合模块安装成功;真实库因 MySQL 1045 未执行 |
| 规范更新 | done | AGENTS.md、CRUD Skill 参考、conventions.md、decisions.md |
后续禁止新增可见辅助生成列 |
| 上下文与 Skill 防回归 | done | CRUD 主 SKILL.md、project-context.md、SQL 参考、检查清单、契约测试 |
建表前强制读取规则;禁止生成列、deleted_at NULL 唯一语义和墓碑固定值 1 |
| 真实库索引漂移修复 | done | V1.0.51__replace_logic_delete_generated_columns.sql、迁移契约测试、Skill SQL 参考和检查清单 |
动态删除实际存在的旧索引;兼容 ai_code_rule 的 uk_ai_code_rule_code 历史替代索引 |
实现与静态审查通过:迁移清单覆盖 55 张表和 64 个唯一索引;54 张数值主键实体使用 MyBatis-Plus 主键墓碑删除,字符串主键表使用专用原子更新;目标契约测试和聚合编译均通过。逻辑删除决策规则已进入根 AGENTS.md、alwaysApply 工程上下文、CRUD 主 Skill、SQL 参考和生成检查清单,并由契约测试防回归。
用户环境首次执行 V1.0.51 时在 ai_code_rule 因历史活跃索引已被 V1.0.36 删除而失败;迁移现已改为发现真实索引后原子替换,并由 5 个契约测试覆盖。数据库仍需先修复 Flyway 失败记录再重跑;执行后按 test-spec.md 的验收 SQL 核对列、索引及删除重建链路。