Skip to content

保持数据一致

另有 28 种语言版本

English · Español · 日本語 · Português (Brasil) · Français · Deutsch · Italiano · 한국어 · Português (Portugal) · 繁體中文 · Bahasa Indonesia · Türkçe · Polski · Tiếng Việt · ไทย · Nederlands · العربية · עברית · Українська · हिन्दी · Русский · Svenska · Dansk · Norsk bokmål · Suomi · Español (México) · Français (Canada) · Deutsch (Schweiz)

An old five-rung wooden ladder against a barn wall, its lowest rungs worn smooth from use and its top rungs barely touched.
图片由 AI 生成

第 9 个模块,共 10 个,属于 Data Architect 课程第 3 章Salesforce 数据架构。每个模块都可以单独阅读。 上一篇:所有权与生命周期 · 下一篇:设计 Salesforce 物理模型

**本模块回答的问题:**我如何保持数据一致,每一层又有什么代价?

每个人都想要干净的数据。我从没在哪次需求会议上听到有人说:“我们真正想要的是不一致的数据。”分歧在后面才出现:我们在哪里执行规则?在界面上?必填字段?选项列表、验证规则、重复规则、Flow、Apex?它们都有帮助。但它们不能互换,而且每一层都有代价。

我的一般规则是:在合理的范围内,把规则放得越低越好。如果一个值永远只能是五种之一,就不要让人随便输入,事后再用自动化去清理。如果两条记录绝不能共用一个标识符,就不要指望有人在报表里发现它。如果一条规则不管记录来自界面、API、集成、数据加载还是智能体都必须成立,就不要只在界面上执行它。规则离数据越近,就越难被绕过。我把它叫作一致性阶梯。

第一条一致性规则就是模块 6 里的那条:**选对类型。**日期用日期,时刻用日期/时间,金额用货币。这是在任何人写规则之前的一致性。

把配送状态做成一个文本字段,你就会得到 Delivered、delivered、DELIVERED、Complete、Done 和 Dropped off,然后得有人去弄清这六个是否是同一个意思。或者一次性把词汇定义好:Delivered,一个值,一个含义。把它做成受限选项列表,这样它会拒绝任何不在列表上的值,即使是通过 API;当多个字段共用这份列表时,就用全局值集(细节见模块 6)。

但不要只是为了不让人打字就用选项列表。它真的是一个值,还是另一个实体?九个各有一个名字的配送中心也许看起来像选项列表;九个有地址、经理、容量和卡车的配送中心就是记录了。规则应该强化模型,而不是弥补一个糟糕的模型。而如果它确实是一套受控词汇,就在字段上控制它。不要先放任混乱,再建一个 Flow 去把 AZ、Arizona、Ariz 和 Phoenix area 变成你一开始就想要的那个值。

必填和唯一很有吸引力,因为它们很简单:勾选复选框,问题解决。有时如此。如果每辆卡车从创建那天起就有车队编号,而且没有两辆卡车共用一个,那么这两个复选框表达的都是真实的业务规则。这是很强的一致性。

但一旦平台执行了一条规则,它就不在乎你的流程在周二下午有个例外。这个例子我在课上用了很多年。一个对象已经存在了一段时间,有大量记录。有人加了一个字段并勾选了必填。他们测试创建一条记录:运行完美。然后周六晚上部署,测试运行,一个旧的测试类在创建这个对象时没有填新字段。砰。或者有人编辑了一条在这个字段存在之前就有的旧记录。或者集成发来了它五年来一直在发的同一个数据包。砰。“可是我们没动那个字段啊。”平台不在乎:你改变了“什么是有效记录”的契约。一条新规则不只影响新数据;它改变了你能对旧数据做什么。

验证规则能让我们表达丰富得多的东西。如果状态是已送达,实际送达时间就必须填写。如果停靠点有安装,检查清单就必须完成后才能关闭停靠点。它们在记录保存时运行,所以无论记录是从页面、API、数据加载、集成还是智能体保存的,它们都能保护记录。这正是关键。如果规则必须保护记录,就把它放在每一次写入都会遇到的地方。

它们也有牙齿。这既是它们的优势,也是它们的代价。

事情在这里变得难看。你有一百万条记录,今天你加了一条规则:送达时间不能早于路线的出发时间。很合理。但有两万条旧记录违反了它,原因是七年前的一次迁移。一个用户打开其中一条,改了电话号码。保存。规则触发了。“我改的是电话号码,你为什么跟我说 2019 年的送达时间?”问得有道理。

所以一条验证规则不应该只说什么必须为真。它还应该说什么时候执行。ISNEW() 和 ISCHANGED() 这样的函数能让你把规则的作用范围限定在新记录上,或者限定在相关字段确实发生变化的保存上(Validation Rule Considerations (opens in a new tab),英文)。

但记录并不只在有人编辑它时才会变化,而其中很多原因不在你的控制之内。集成在夜里更新状态。一个 Flow 盖上一个日期。一位经理重新分配了一个区域,一万条记录的所有人随之改变。有人编辑了一个配送项目,配送上的汇总字段重新计算,这就保存了配送,于是配送的验证规则为一个从没碰过它的用户触发了(Roll-Up Summary Field (opens in a new tab)(英文)提醒,验证错误可能显示在从记录上,也可能显示在主记录上)。每一次这样的保存都会遇到你的新规则。所以对每一条新规则,都要问:这条规则应该适用于那次保存吗?

这些就是可选的做法,你把它们编织进规则里:

  • 始终执行,每次保存都执行。当规则确实关乎记录本身、而旧数据也能通过时,这是对的。但要明白“每次保存”意味着什么:一个只更改所有人的批处理作业,或者对记录上任何字段的任何小修改,也都必须通过它。
  • 只针对新记录,用 ISNEW()。规则在记录创建时检查一次,之后再也不检查。过去保持原样,但未来也是如此:之后的一次编辑可能让一条新记录违反规则,却没有任何东西会阻止它。如果这很重要,就把它和下一种配合使用。
  • 只在它所关乎的字段变化时执行,用 ISCHANGED()。编辑电话号码不会唤醒送达时间那条规则。
  • **对某些写入者不执行。**一个自定义权限,在公式中用 $Permission 检查,可以让集成用户或一次数据迁移通过,而人仍然要满足规则(How to Reference a Custom Permission in a Validation Rule (opens in a new tab),英文)。要有意识地授予它,而且只授予少数人。
  • 从某个日期开始执行,把 CreatedDate 与规则上线的日期作比较,适用于“新”意味着“自从我们开始执行这条规则以来”的情况。与 ISNEW() 不同,它在之后每次保存时都会继续检查这些记录,所以同样的提醒也适用:一个小改动,比如批量更新所有人,可能会在一条已经偏离规则的记录上失败。

**要给已有的记录加一条规则?**在开启之前想清楚后果。每一条现有记录在下一次保存时都必须通过它,而那次保存可能来自你意想不到的地方:一个夜间集成、一次批量更换所有人、一次汇总、一次从列表视图发起的批量更新。其中哪些会碰到它?当一条记录失败时,那个作业会怎样?在开启之前一节里有我使用的顺序。

不要为了让规则满意而改写历史

Section titled “不要为了让规则满意而改写历史 ”

那两万条旧记录怎么办?如果数据是错的,而你知道正确的值,就更正它。但我见过另一种“修复”:没人知道历史的真相,他们只是需要这些记录能通过,于是用满足今天规则的值去更新它们。我不喜欢这样。为了合规去更新旧记录,会改变 LastModifiedDate,毁掉关键的历史信息,而编造值就意味着假装我们知道一些关于过去其实并不知道的事。给规则一个针对它之前就存在的记录的绕过机制,而不是改写历史。这也许难看,但它是诚实的。

在页面布局上把一个字段设为必填,用户不填就无法保存。然后夜间集成插入了 5 万条记录。它用了你的页面吗?没有。批量加载、API 客户端和智能体也都没有。界面上的必填要求保护的是那个界面,而不是数据。动态表单上的字段验证也可以用同样的方式被绕过。所以要问:**这些数据能从哪里进来?**Lightning、门户、移动端、集成、API、批量加载、Flow、Apex、智能体。门越多,我就越不信任一条只守着其中一扇门的规则。

现在我们从记录内部的一致性,转向记录之间的一致性。Acme Corporation、ACME Corp、Acme Inc.:是四个客户还是一个?没有哪条字段规则能回答这个问题。匹配规则定义记录如何比较;重复规则决定发现可能的重复时怎么办:允许并给出警告、阻止,或者报告。每个对象最多可以有五条有效的重复规则,每条规则最多三条匹配规则,而匹配键会把每次比较缩小到最可能重复的 100 条记录(Things to Know About Duplicate Rules (opens in a new tab),英文)。

它们的代价是摩擦。如果销售代表创建的每个客户都提示发现可能的重复,有时这正是你想要的,有时人们会学会不看就点过去。合法的记录可能看起来很像,而集成可能需要和人不同的行为。而且这个警告是 Salesforce 自己的:它的提醒和可能重复的列表,可能和你构建的自定义界面并不协调;而当保存是无界面的(headless),比如集成、数据加载、智能体,就根本没有可以显示它的界面。所以要决定重复在每一条路径上如何呈现:对人给出警告,对 API 阻止保存并返回错误,或者用“报告”选项和重复记录集,留给某人以后审查。好的架构不是最大程度的执行,而是恰当的执行。

还要检查一条重复规则实际守住了哪几扇门(Things to Know About Duplicate Rules (opens in a new tab),英文)。通过 API 和数据导入工具时,没人会看到警告:设为提醒的规则会直接阻止保存。有些路径会完全跳过这些规则:快速创建、“恢复”按钮、手动合并、由 Lightning Sync 或 Einstein Activity Capture 添加的记录。而且同一时刻保存的记录不会彼此比较,只会与 Salesforce 中已有的记录比较。加载一个包含同一客户两次的文件,两行都可能通过。重复规则是一张好网。它不是门上的锁。

考点。重复规则不会把同一时刻保存的记录彼此比较,只会与 Salesforce 中已有的记录比较。通过 API 和导入工具时没有提醒:被规则拦下的重复记录只是不会被保存。而一次保存以及它引发的所有操作作为一个事务运行:任何一部分失败,全部回滚。

有一条边界。重复规则保持的是一个 org 的干净,它们解决不了企业层面的身份问题。如果 Salesforce 里有 Acme Corporation,ERP 里有 ACME-10382,计费系统里有客户 77193,重复规则无法告诉你这些是同一家公司。那是主数据的问题,第 6 章。

有些规则对较低的几级来说太复杂了:它们需要相关记录、另一个系统、依赖记录、一个多步骤的流程。那就是 Flow、Apex,有时还有集成逻辑。(不是工作流规则或 Process Builder;它们已不再受支持。)自动化很强大,也更昂贵:更多的活动部件,更多的测试,更多的错误处理,以及一个自动化绊倒另一个自动化的更多可能。而代码会带来它自己的错误。如果字段类型能执行,我就不写 Apex。如果一条验证规则能把它说清楚,我就不会因为有 Flow 就去建一个 Flow。不要用流程引擎去解决一个字段问题。

全有或全无:当记录一起变化时

Section titled “全有或全无:当记录一起变化时 ”

到目前为止,每一级保护的是一次保存。但记录会一起变化:一次配送和它的项目,一个订单和它的明细行,一个更新三个对象的 Flow。在这里,平台给了你一个很强的保证。一次保存以及它引发的所有操作,包括触发器、Flow 以及它们调用的类,都作为一个事务运行:任何一部分失败,整个事务都会回滚,什么都不会提交(Apex Transactions (opens in a new tab),英文)。你不会得到半次配送。

批量操作是你主动选择的例外。数据加载或 Apex 的 Database 方法可以允许部分成功:好的记录保存下来,坏的记录带着错误返回,然后得有人处理它们(DML Statements vs. Database Class Methods (opens in a new tab),英文)。对一次一百万行的加载来说,这往往是对的,但这时一致性就成了一个流程:谁来读错误文件,失败的记录又怎么办?

然后是时机。同一时刻的两次保存,可能都通过了一项各自单独时会失败的检查。一个先查找已有订单、再创建订单的 Flow,可能会放过两个,而我们刚刚看到,重复规则不会比较同时保存的记录。一个标记为唯一的字段,或者一个外部 ID,在每次保存时都由平台检查,所以只有一个能胜出。这又是一个把唯一性下沉到字段上的理由。锁、争用和重试是一个单独的话题;第 11 章 事务、并发与可靠性 会深入讨论。

把规则往下放,直到它不再属于那里

Section titled “把规则往下放,直到它不再属于那里 ”

“在合理的范围内越低越好”并不意味着“总是最低”。如果一个字段只有在业务机会到达某个阶段后才是必填的,那么在结构中把它设为必填就是错的;这条规则是有条件的,所以验证规则才是正确的那一级。如果一个重复应该提醒人,但一个经过批准的集成有它自己的对账机制,那就为两条路径都做设计。目标是:能正确表达业务规则的最低一层。

一致性阶梯 以矩阵形式呈现的一致性阶梯,从最低一级开始,对照四项检验:每一条写入路径、丰富的条件、跨记录、易于共处。第 1 级,字段类型和选项列表:在每一条路径上都成立,包括界面、API、加载和智能体,也易于共处,但只能表达一个字段能表达的东西;最强。第 2 级,必填和唯一:每一条路径都成立,唯一还能跨记录生效,但它始终生效,没有例外;生硬。第 3 级,验证规则:每一条路径都成立,条件丰富,能查看记录及其父记录,但必须为旧记录限定范围;限定范围。第 4 级,重复规则:跨记录的模糊匹配,但它会跳过合并和恢复,还会增加摩擦,通过 API 时没有提示;一张网。第 5 级,Flow 或 Apex 中的自动化:若由记录触发,什么都能做,但测试、错误和维护都归你负责;最后手段。还有作为对照的页面布局必填要求:只保护这个界面;不是数据。底部:把规则往下放到能正确表达它的最低一级。 每一条写入路径 丰富的条件 跨记录 易于共处 它是 字段类型和选项列表 第 1 级:词汇 界面、API、加载、智能体 只限字段能表达的 最强 必填和唯一 第 2 级:字段 始终生效,没有例外 唯一:一条胜出 生硬 验证规则 第 3 级:公式 记录及其父记录 限定范围:旧记录 限定范围 重复规则 第 4 级:匹配 跳过合并、恢复 模糊匹配 有摩擦,API 无提示 一张网 自动化 第 5 级:Flow、Apex 若由记录触发 测试、错误、维护 最后手段 页面布局必填 对照:一个界面 仅限此界面 不是数据 把规则往下放到能正确表达它的最低一级。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

这是我希望你看到的权衡。字段类型和受控值简单而有力,但只限于一个字段能表达的东西。必填和唯一很生硬:一旦开启,每一次写入都必须满足它们。验证规则能表达更丰富的条件,但可能阻止对旧记录的编辑,并让一次部署措手不及。重复规则跨记录提供保护,但给用户和集成增加了摩擦。自动化什么都能处理,但你要为编排、错误、测试和性能负责。没有哪一层是免费的。在知道它会如何失效的前提下,为每条规则选择它所在的层。

在一个已有的 org 中添加一条规则是一次推广,而不是勾一个复选框。今天有多少条记录违反它,为什么?它们真的错了吗?我们能知道正确的值吗?这条规则对历史数据也成立,还是只从现在开始成立?哪些集成、测试和自动化会碰到它?一个好的顺序是:度量,清理那些你能证明是错的,限定规则的范围,为合法的历史数据有意地加一个绕过机制,测试每一条写入路径,然后再执行。度量和清理本身就是专门的学问,包括数据剖析、标准化、监控和数据管理,第 5 章 数据质量与治理 会讲到它们。这比周五勾一个复选框、周六晚上才发现后果要好得多。

**考试之外:AI 改变了什么。**智能体创建记录的速度比任何人都快,创建坏记录也一样快。当用户是一个智能体时,“用户知道不要那样做”就算不上一种保护。把重要的规则放在数据层,让每一个写入者都会遇到它们,这样智能体就不必去记住公司那些不成文的规矩。

拿一次配送来说。它的状态来自一套词汇:一个选项列表,受限的。零售合作伙伴的订单编号在每个合作伙伴内必须唯一,所以它是一个外部 ID,标记为唯一,用于对装货清单做 upsert。状态变为已送达后,实际送达时间就是必填的:一条验证规则,限定在这个变化上。一个安装停靠点要等检查清单完成、所需照片都拍好(对于洗碗机,是进水管、排水管和水平)才能关闭:一条验证规则,如果要检查存放在平台之外的照片,就用自动化。同一个订单因为装货清单重发而到达两次,会被外部 ID 拦住;两个看起来很像的不同订单,则是一个匹配问题。

注意我没有说“把它们全放进一个 Flow 里”。每条规则都待在它自己的那一级上。对每一条规则,都问一问:对每一条重要的写入路径来说,能正确执行它的最低一层是什么?

  • 每条规则属于哪一级:字段类型、受控词汇、必填或唯一、验证规则、重复规则,还是自动化。
  • 哪些写入路径必须受到保护:界面、门户、移动端、集成、批量加载、API、自动化和智能体。
  • 一条新规则是否适用于历史记录,还是限定在新数据和变化的数据上,并为过去的数据提供一个绕过机制。
  • 现有的坏数据能否根据证据来更正,而不是为了通过检验去改写。
  • 一条规则值得付出多少摩擦、复杂度和维护成本。

干净的数据不是靠要求人们输入好数据得来的。它来自一种架构,让正确的事情容易做,让错误的事情难做,让重要的规则难以绕过。接下来,我们在 Steve Trucking 的一个切面上把本章的内容串起来。