semantic-fk.md 3.43 KB

无物理外键、语义外键的现实

xlyweberp_saas_ai schema 在任何 xly 自研表(gds*ele*mft*quo*sal*acc* 等表族)上都有 0 个触发器0 个外键约束。确实存在的 41 个 FK 约束都在捆绑的第三方 schema 上:Activiti 表(act_*)36 个、Quartz 调度表(qrtz_*)5 个;框架自身运行时不会通过这些表做核心连接。在 901 张基础表中,框架依赖的元数据和业务数据连接全部只是约定。

这是一个有意的设计选择,继续阅读前必须理解。

为什么 xly 禁用 FK

架构上给出的两个原因都很务实:

  1. 批量写入性能。 大量插入(工单计算、月结、批量导入)一次会写入几十万行。启用 FK 后,MySQL 会在插入时验证每一行引用;以 xly 的数据量,这会成为限制因素。
  2. schema 迁移敏捷性。 xly 演进很快:新模块、新字段、新表。启用 FK 时,每次 schema 变更都必须考虑约束图;没有 FK 时,CREATE TABLEALTER TABLE 是局部操作。代价由运行时应用代码承担。

什么是“语义 FK”

语义 FK 指如果启用 FK 本该成为外键、但实际上没有约束的列。关系编码在:

  • 列命名。 sCustomerId 被理解为引用 eleCustomer.sId。从表上的 sParentId 被理解为引用主表的 sId
  • 存储过程和 MyBatis mapper 中的共同出现。 运行时在很多地方连接 A.sParentId = B.sId;阅读过程体能发现哪些表配对。
  • 列注释。 许多列填了 COLUMN_COMMENT,并常常指出引用概念,例如 加工商ID
  • 元数据声明。 框架的 gdsconfigformslave.sActiveId / sActiveKey 会明确写出下拉控件指向的表单字段,这是一种元数据编码的 FK。

这些都不会被数据库强制。你可以从 eleCustomer 删除一行,数据库不会阻止;孤儿 sCustomerId 引用会继续存在。

恢复关系

当自动目录或 Wiki 需要知道“列 X 引用什么”时,查找流程是:

  1. 读列注释,通常这是最有帮助的一行。
  2. 在 schema 中搜索匹配的 ID 列(在 recon/columns.tsvgrep XSomethingId)。
  3. 在代码库中搜索在该列上 JOIN 的 SQL 片段。
  4. 检查 gdsconfigformslave.sActiveId,框架会在这里记录下拉和 lookup。
  5. 最后再按主从命名约定推断。

自动目录的目标就是简化这些工作:每个生成的表页列出字段,并在未来增强中列出引用它们的过程,从而缩小搜索范围。

失效模式

禁用 FK 的成本在运行时体现。三种失效模式反复出现:

  • 孤儿行。 比如客户被删除后仍存在的 sCustomerId。框架查询通过 left join 穿过这些行,可能静默丢弃,也可能显示空白。
  • 跨租户 ID 不匹配。 没有 FK 时,没有东西阻止一行引用另一个 sBrandsId 下的 ID。多租户过滤(切片 2)是阻止它演变成跨租户泄漏的唯一边界。
  • 需要手动验证引用完整性的存储过程。 许多 xly 存储过程在插入前显式检查 EXISTS (...)。本该由 FK 自动完成的完整性工作散布在过程体中。漏掉一次,bug 可能几周后才暴露。

本 Wiki 的任务是在数据库不强制的情况下暴露这些关系。大部分暴露发生在自动目录和切片交叉引用中;本页是“为什么没有 FK、应该信什么”的规范参考。