保持数据一致
另有 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)

第 9 个模块,共 10 个,属于 Data Architect 课程第 3 章Salesforce 数据架构。每个模块都可以单独阅读。 上一篇:所有权与生命周期 · 下一篇:设计 Salesforce 物理模型
**本模块回答的问题:**我如何保持数据一致,每一层又有什么代价?
每个人都想要干净的数据。我从没在哪次需求会议上听到有人说:“我们真正想要的是不一致的数据。”分歧在后面才出现:我们在哪里执行规则?在界面上?必填字段?选项列表、验证规则、重复规则、Flow、Apex?它们都有帮助。但它们不能互换,而且每一层都有代价。
我的一般规则是:在合理的范围内,把规则放得越低越好。如果一个值永远只能是五种之一,就不要让人随便输入,事后再用自动化去清理。如果两条记录绝不能共用一个标识符,就不要指望有人在报表里发现它。如果一条规则不管记录来自界面、API、集成、数据加载还是智能体都必须成立,就不要只在界面上执行它。规则离数据越近,就越难被绕过。我把它叫作一致性阶梯。
第一级:字段类型和词汇
Section titled “第一级:字段类型和词汇 ”第一条一致性规则就是模块 6 里的那条:**选对类型。**日期用日期,时刻用日期/时间,金额用货币。这是在任何人写规则之前的一致性。
把配送状态做成一个文本字段,你就会得到 Delivered、delivered、DELIVERED、Complete、Done 和 Dropped off,然后得有人去弄清这六个是否是同一个意思。或者一次性把词汇定义好:Delivered,一个值,一个含义。把它做成受限选项列表,这样它会拒绝任何不在列表上的值,即使是通过 API;当多个字段共用这份列表时,就用全局值集(细节见模块 6)。
但不要只是为了不让人打字就用选项列表。它真的是一个值,还是另一个实体?九个各有一个名字的配送中心也许看起来像选项列表;九个有地址、经理、容量和卡车的配送中心就是记录了。规则应该强化模型,而不是弥补一个糟糕的模型。而如果它确实是一套受控词汇,就在字段上控制它。不要先放任混乱,再建一个 Flow 去把 AZ、Arizona、Ariz 和 Phoenix area 变成你一开始就想要的那个值。
第二级:必填和唯一
Section titled “第二级:必填和唯一 ”必填和唯一很有吸引力,因为它们很简单:勾选复选框,问题解决。有时如此。如果每辆卡车从创建那天起就有车队编号,而且没有两辆卡车共用一个,那么这两个复选框表达的都是真实的业务规则。这是很强的一致性。
但一旦平台执行了一条规则,它就不在乎你的流程在周二下午有个例外。这个例子我在课上用了很多年。一个对象已经存在了一段时间,有大量记录。有人加了一个字段并勾选了必填。他们测试创建一条记录:运行完美。然后周六晚上部署,测试运行,一个旧的测试类在创建这个对象时没有填新字段。砰。或者有人编辑了一条在这个字段存在之前就有的旧记录。或者集成发来了它五年来一直在发的同一个数据包。砰。“可是我们没动那个字段啊。”平台不在乎:你改变了“什么是有效记录”的契约。一条新规则不只影响新数据;它改变了你能对旧数据做什么。
第三级:验证规则
Section titled “第三级:验证规则 ”验证规则能让我们表达丰富得多的东西。如果状态是已送达,实际送达时间就必须填写。如果停靠点有安装,检查清单就必须完成后才能关闭停靠点。它们在记录保存时运行,所以无论记录是从页面、API、数据加载、集成还是智能体保存的,它们都能保护记录。这正是关键。如果规则必须保护记录,就把它放在每一次写入都会遇到的地方。
它们也有牙齿。这既是它们的优势,也是它们的代价。
旧记录问题
Section titled “旧记录问题 ”事情在这里变得难看。你有一百万条记录,今天你加了一条规则:送达时间不能早于路线的出发时间。很合理。但有两万条旧记录违反了它,原因是七年前的一次迁移。一个用户打开其中一条,改了电话号码。保存。规则触发了。“我改的是电话号码,你为什么跟我说 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,毁掉关键的历史信息,而编造值就意味着假装我们知道一些关于过去其实并不知道的事。给规则一个针对它之前就存在的记录的绕过机制,而不是改写历史。这也许难看,但它是诚实的。
界面验证不等于数据验证
Section titled “界面验证不等于数据验证 ”在页面布局上把一个字段设为必填,用户不填就无法保存。然后夜间集成插入了 5 万条记录。它用了你的页面吗?没有。批量加载、API 客户端和智能体也都没有。界面上的必填要求保护的是那个界面,而不是数据。动态表单上的字段验证也可以用同样的方式被绕过。所以要问:**这些数据能从哪里进来?**Lightning、门户、移动端、集成、API、批量加载、Flow、Apex、智能体。门越多,我就越不信任一条只守着其中一扇门的规则。
第四级:重复规则
Section titled “第四级:重复规则 ”现在我们从记录内部的一致性,转向记录之间的一致性。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 章。
第五级:自动化
Section titled “第五级:自动化 ”有些规则对较低的几级来说太复杂了:它们需要相关记录、另一个系统、依赖记录、一个多步骤的流程。那就是 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 “把规则往下放,直到它不再属于那里 ”“在合理的范围内越低越好”并不意味着“总是最低”。如果一个字段只有在业务机会到达某个阶段后才是必填的,那么在结构中把它设为必填就是错的;这条规则是有条件的,所以验证规则才是正确的那一级。如果一个重复应该提醒人,但一个经过批准的集成有它自己的对账机制,那就为两条路径都做设计。目标是:能正确表达业务规则的最低一层。
每一层都有代价
Section titled “每一层都有代价 ”这是我希望你看到的权衡。字段类型和受控值简单而有力,但只限于一个字段能表达的东西。必填和唯一很生硬:一旦开启,每一次写入都必须满足它们。验证规则能表达更丰富的条件,但可能阻止对旧记录的编辑,并让一次部署措手不及。重复规则跨记录提供保护,但给用户和集成增加了摩擦。自动化什么都能处理,但你要为编排、错误、测试和性能负责。没有哪一层是免费的。在知道它会如何失效的前提下,为每条规则选择它所在的层。
在开启之前
Section titled “在开启之前 ”在一个已有的 org 中添加一条规则是一次推广,而不是勾一个复选框。今天有多少条记录违反它,为什么?它们真的错了吗?我们能知道正确的值吗?这条规则对历史数据也成立,还是只从现在开始成立?哪些集成、测试和自动化会碰到它?一个好的顺序是:度量,清理那些你能证明是错的,限定规则的范围,为合法的历史数据有意地加一个绕过机制,测试每一条写入路径,然后再执行。度量和清理本身就是专门的学问,包括数据剖析、标准化、监控和数据管理,第 5 章 数据质量与治理 会讲到它们。这比周五勾一个复选框、周六晚上才发现后果要好得多。
**考试之外:AI 改变了什么。**智能体创建记录的速度比任何人都快,创建坏记录也一样快。当用户是一个智能体时,“用户知道不要那样做”就算不上一种保护。把重要的规则放在数据层,让每一个写入者都会遇到它们,这样智能体就不必去记住公司那些不成文的规矩。
Steve Trucking:保护配送
Section titled “Steve Trucking:保护配送 ”拿一次配送来说。它的状态来自一套词汇:一个选项列表,受限的。零售合作伙伴的订单编号在每个合作伙伴内必须唯一,所以它是一个外部 ID,标记为唯一,用于对装货清单做 upsert。状态变为已送达后,实际送达时间就是必填的:一条验证规则,限定在这个变化上。一个安装停靠点要等检查清单完成、所需照片都拍好(对于洗碗机,是进水管、排水管和水平)才能关闭:一条验证规则,如果要检查存放在平台之外的照片,就用自动化。同一个订单因为装货清单重发而到达两次,会被外部 ID 拦住;两个看起来很像的不同订单,则是一个匹配问题。
注意我没有说“把它们全放进一个 Flow 里”。每条规则都待在它自己的那一级上。对每一条规则,都问一问:对每一条重要的写入路径来说,能正确执行它的最低一层是什么?
架构师在这里决定什么
Section titled “架构师在这里决定什么 ”- 每条规则属于哪一级:字段类型、受控词汇、必填或唯一、验证规则、重复规则,还是自动化。
- 哪些写入路径必须受到保护:界面、门户、移动端、集成、批量加载、API、自动化和智能体。
- 一条新规则是否适用于历史记录,还是限定在新数据和变化的数据上,并为过去的数据提供一个绕过机制。
- 现有的坏数据能否根据证据来更正,而不是为了通过检验去改写。
- 一条规则值得付出多少摩擦、复杂度和维护成本。
干净的数据不是靠要求人们输入好数据得来的。它来自一种架构,让正确的事情容易做,让错误的事情难做,让重要的规则难以绕过。接下来,我们在 Steve Trucking 的一个切面上把本章的内容串起来。
- Salesforce Help,How to Reference a Custom Permission in a Formula, Workflow Rule, or Validation Rule (opens in a new tab) 和 Roll-Up Summary Field (opens in a new tab)(英文),查阅日期 2026-10-13。
- Salesforce Help,Picklist Limitations (opens in a new tab)(英文),查阅日期 2026-10-02。
- Salesforce Help,Validation Rule Considerations (opens in a new tab)(英文),查阅日期 2026-10-02。
- Salesforce Help,Things to Know About Duplicate Rules (opens in a new tab)(英文),查阅日期 2026-10-13:各项限制、规则不运行的路径、通过 API 和导入保存时无提醒地阻止、同时保存的记录不相互比较。
- Apex Developer Guide,Apex Transactions (opens in a new tab) 和 DML Statements vs. Database Class Methods (opens in a new tab)(英文),查阅日期 2026-10-13:一个事务作为一个整体回滚;Database 方法可以允许部分成功。
- Salesforce Help,Workflow Rules & Process Builder End of Support (opens in a new tab)(英文),查阅日期 2026-10-02。
