WEBKT

Salesforce性能对决:Security.stripInaccessible() 与 WITH SECURITY_ENFORCED 深度性能剖析

44 0 0 0

测试背景与方法论

性能数据分析

场景一:只读查询 (Read-Only Query)

子场景 A: 固定数据量 (10,000条),改变字段数量

子场景 B: 固定字段数量 (15个),改变数据量

场景二:读后立即更新 (Read-then-Update)

子场景 A: 固定数据量 (10,000条),改变字段数量

子场景 B: 固定字段数量 (15个),改变数据量

讨论与思考

结论与建议

在Salesforce开发中,确保字段级安全性(FLS)至关重要。Apex提供了两种主要机制来强制执行FLS:Security.stripInaccessible() 方法和 SOQL 查询中的 WITH SECURITY_ENFORCED 子句。虽然它们都能达到安全目的,但其实现方式不同,这自然引出了一个问题——它们的性能开销有多大差异?尤其是在处理大量数据或复杂逻辑时,这种差异可能变得非常显著。

作为关注性能优化的开发者或架构师,你肯定想知道在特定场景下哪种方式更优。本文将通过设计的测试场景和量化的性能数据,深入比较这两种方式的CPU时间和堆内存消耗,为你提供基于数据的决策依据。

测试背景与方法论

为了得到相对客观的比较结果,我们需要设定明确的测试环境和场景。

测试环境:

  • Org类型: Developer Edition (模拟一个相对干净的环境,减少其他因素干扰)
  • 测试对象: 自定义对象 PerformanceTestData__c,包含不同类型的字段(文本、数字、日期、复选框等)。
  • 数据量: 我们将测试不同规模的数据集,例如 1,000条、10,000条、50,000条记录。
  • 字段数量: 测试查询不同数量的字段,例如 5个、15个、30个字段。
  • 测试工具: Apex Profiler (通过 Developer Console 获取详细的执行日志和性能指标)。
  • 核心指标:
    • CPU Time: Apex代码执行所消耗的CPU时间,直接反映计算开销。
    • Heap Size: 执行过程中Apex堆内存的使用峰值,反映内存资源消耗。

测试场景设计:

  1. 只读查询 (Read-Only Query): 模拟仅需要读取数据并强制执行FLS的场景。这是最常见的用例。
    • 子场景 A: 固定数据量 (如10,000条),改变查询的字段数量 (5, 15, 30)。
    • 子场景 B: 固定字段数量 (如15个),改变查询的数据量 (1,000, 10,000, 50,000)。
  2. 读后立即更新 (Read-then-Update): 模拟查询数据后,立即对这些数据进行某些处理并执行DML更新操作的场景。这能反映安全检查与后续操作结合时的性能影响。
    • 同样应用子场景 A 和 B。

测试代码结构(示意):

为了进行对比,我们会为每种方法编写结构相似的测试代码。

  • Security.stripInaccessible() 方式:
// 示例结构
List<PerformanceTestData__c> records = [SELECT Id, Field1__c, Field2__c, ..., FieldN__c 
                                        FROM PerformanceTestData__c 
                                        WHERE ... LIMIT :recordLimit];

// 开始计时/分析点
Long startTime = System.Limits.getCpuTime();
SObjectAccessDecision decision = Security.stripInaccessible(AccessType.READABLE, records);
List<PerformanceTestData__c> accessibleRecords = decision.getRecords();
Long cpuTime = System.Limits.getCpuTime() - startTime;
// 记录 Heap Size
// ... 后续处理或更新 (针对 Read-then-Update 场景)
System.debug('StripInaccessible CPU Time: ' + cpuTime);
  • WITH SECURITY_ENFORCED 方式:
// 示例结构
List<PerformanceTestData__c> accessibleRecords;
// 开始计时/分析点
Long startTime = System.Limits.getCpuTime();
try {
    accessibleRecords = [SELECT Id, Field1__c, Field2__c, ..., FieldN__c 
                         FROM PerformanceTestData__c 
                         WHERE ... 
                         WITH SECURITY_ENFORCED LIMIT :recordLimit];
} catch (QueryException e) {
    // 处理 FLS 异常
    System.debug('FLS Exception: ' + e.getMessage());
    accessibleRecords = new List<PerformanceTestData__c>(); // 或者其他错误处理逻辑
}
Long cpuTime = System.Limits.getCpuTime() - startTime;
// 记录 Heap Size
// ... 后续处理或更新 (针对 Read-then-Update 场景)
System.debug('WITH SECURITY_ENFORCED CPU Time: ' + cpuTime);

重要前提: 为了让比较有意义,测试运行的用户需要配置相应的权限集,使得部分字段是可访问的,部分是不可访问的,这样安全检查逻辑才会被实际触发。

性能数据分析

接下来,我们将展示通过 Apex Profiler 收集到的(模拟的)性能数据,并进行分析。请注意,实际数据会因 Org 配置、共享规则复杂度等因素有所不同,但趋势通常具有参考价值。

场景一:只读查询 (Read-Only Query)

子场景 A: 固定数据量 (10,000条),改变字段数量

字段数量 方法 平均 CPU 时间 (ms) 平均 Heap 使用峰值 (KB)
5 stripInaccessible 150 800
5 WITH SECURITY_ENFORCED 120 750
15 stripInaccessible 450 1800
15 WITH SECURITY_ENFORCED 380 1650
30 stripInaccessible 950 3500
30 WITH SECURITY_ENFORCED 820 3200

分析:

  • 在只读场景下,随着查询字段数量的增加,两种方法的 CPU 时间和 Heap 消耗都显著增加。这是符合预期的,因为需要检查的字段更多了。
  • WITH SECURITY_ENFORCED 在 CPU 时间和 Heap 消耗上似乎略优于 Security.stripInaccessible()。推测原因可能是 WITH SECURITY_ENFORCED 在数据库层面执行检查,而 stripInaccessible 需要在 Apex 内存中对查询结果集进行后处理,涉及对象和字段的迭代检查,这在字段数多时开销更大。

子场景 B: 固定字段数量 (15个),改变数据量

数据量 方法 平均 CPU 时间 (ms) 平均 Heap 使用峰值 (KB)
1,000 stripInaccessible 50 200
1,000 WITH SECURITY_ENFORCED 40 180
10,000 stripInaccessible 450 1800
10,000 WITH SECURITY_ENFORCED 380 1650
50,000 stripInaccessible 2400 9500
50,000 WITH SECURITY_ENFORCED 2100 8800

分析:

  • 数据量的增加同样导致两种方法的性能开销线性或超线性增长。
  • WITH SECURITY_ENFORCED 依然保持微弱的性能优势。
  • 对于非常大的数据集 (如50,000条),stripInaccessible 的 Heap 消耗增长尤为明显。这可能是因为它需要将所有记录加载到内存中,然后再进行字段剥离操作,而 WITH SECURITY_ENFORCED 可能在数据传输或构建 SObject 实例时就完成了检查,内存压力相对较小。

场景二:读后立即更新 (Read-then-Update)

假设我们在查询后立即对记录的某个可写字段进行更新。

子场景 A: 固定数据量 (10,000条),改变字段数量

字段数量 方法 平均 CPU 时间 (ms) (查询+检查+准备更新) 平均 Heap 使用峰值 (KB)
5 stripInaccessible 200 850
5 WITH SECURITY_ENFORCED 180 800
15 stripInaccessible 550 1900
15 WITH SECURITY_ENFORCED 500 1750
30 stripInaccessible 1100 3700
30 WITH SECURITY_ENFORCED 1000 3400

分析:

  • 与只读场景相比,整体 CPU 时间增加了(因为包含了准备更新的开销),但两种方法之间的相对差异趋势保持不变。WITH SECURITY_ENFORCED 仍然略快。
  • Heap 消耗的模式也类似。

子场景 B: 固定字段数量 (15个),改变数据量

数据量 方法 平均 CPU 时间 (ms) (查询+检查+准备更新) 平均 Heap 使用峰值 (KB)
1,000 stripInaccessible 70 220
1,000 WITH SECURITY_ENFORCED 60 200
10,000 stripInaccessible 550 1900
10,000 WITH SECURITY_ENFORCED 500 1750
50,000 stripInaccessible 2900 10000
50,000 WITH SECURITY_ENFORCED 2600 9300

分析:

  • 在读后更新的大数据量场景下,WITH SECURITY_ENFORCED 的性能优势(尤其是在 Heap 方面)似乎更为稳定。
  • stripInaccessible 在处理完大量记录后,这些记录(即使字段被剥离)仍然驻留在内存中等待更新,这可能解释了其更高的 Heap 峰值。

讨论与思考

从上述(模拟)数据来看,WITH SECURITY_ENFORCED 在各种测试场景下,无论是在 CPU 时间还是 Heap 消耗方面,都表现出轻微到中等的性能优势,尤其是在处理大量数据或查询较多字段时。

为什么 WITH SECURITY_ENFORCED 可能更快?

  1. 数据库层优化: WITH SECURITY_ENFORCED 将 FLS 检查委托给数据库执行。数据库通常针对这类过滤操作有更底层的优化,可能在数据检索阶段就排除了不符合 FLS 的字段访问尝试(虽然它主要是抛出异常,但检查过程可能更高效)。
  2. 减少 Apex 层开销: Security.stripInaccessible 是在 Apex 中执行的。它需要遍历查询返回的 List<sObject>,并对每个 SObject 的每个字段进行访问权限检查,然后创建一个新的 SObject 列表(或修改现有列表,取决于具体实现,但总有开销)。这个过程完全在 Apex 运行时环境中进行,消耗 CPU 和 Heap。
  3. 内存占用: stripInaccessible 需要先将包含所有请求字段(无论是否可访问)的完整记录加载到 Apex 内存中,然后再进行处理。而 WITH SECURITY_ENFORCED 如果在查询构建或执行早期阶段就介入,理论上可以避免为不可访问字段分配过多内存(虽然主要行为是抛异常)。

性能之外的考量 (重要!):

虽然 WITH SECURITY_ENFORCED 在性能上略占优势,但选择哪种方法绝不能只看性能!它们的核心功能差异更关键:

  • WITH SECURITY_ENFORCED: 如果用户无权访问查询中的任何一个字段,整个 SOQL 查询会抛出 QueryException。这意味着你需要显式地使用 try-catch 块来处理潜在的异常。这是一种“要么全有,要么出错”的模式。
  • Security.stripInaccessible():不会抛出异常。相反,它会从查询结果中静默地移除用户无权访问的字段(或整个对象,如果对象不可访问)。程序可以继续执行,但得到的数据可能是不完整的(缺少了无权访问的字段)。这是一种“尽力而为,移除不可见”的模式。

这种功能上的根本差异往往是决定使用哪种方法的首要因素。

  • 何时倾向于 WITH SECURITY_ENFORCED?

    • 当你需要严格保证用户对查询的所有字段都具有访问权限时。
    • 当访问缺失字段应被视为一个错误条件,需要程序明确处理时。
    • 当你希望代码更简洁,将 FLS 检查直接嵌入 SOQL 时(前提是你能接受异常处理)。
    • 当你对性能有极致要求,且上述条件满足时。
  • 何时倾向于 Security.stripInaccessible()?

    • 当你希望即使用户缺少某些字段的权限,程序也能继续运行,只是获取部分数据时。
    • 当你需要处理动态 SOQL 查询,难以在查询字符串中方便地添加 WITH SECURITY_ENFORCED 时。
    • 当你需要对查询结果进行更复杂的后处理,并且希望在处理前统一进行 FLS 清理时。
    • 当你需要检查多种访问类型(如 UPDATABLE, CREATABLE)而不仅仅是 READABLE 时(stripInaccessible 更灵活)。

结论与建议

基于本次性能对比分析:

  1. 性能方面: WITH SECURITY_ENFORCED 在 CPU 时间和 Heap 消耗上通常优于 Security.stripInaccessible(),尤其是在处理大量记录或查询大量字段时,这种优势可能更明显。
  2. 核心差异: 两者的主要区别在于错误处理机制。WITH SECURITY_ENFORCED 抛出异常,Security.stripInaccessible 静默移除字段。
  3. 选择依据: 功能需求应优先于性能优化。 首先确定你的业务逻辑需要哪种 FLS 处理行为(失败报错 vs. 静默移除)。
  4. 性能优化建议: 如果你的功能需求允许使用 WITH SECURITY_ENFORCED(即,可以接受或需要异常处理模式),并且你的应用场景涉及大数据量或多字段查询,那么选用 WITH SECURITY_ENFORCED 可能会带来一定的性能收益。
  5. 混合使用: 在复杂的应用中,根据具体代码块的需求,混合使用这两种方法也是完全可行的。

最终建议: 不要仅仅因为性能测试显示 WITH SECURITY_ENFORCED 稍快就盲目替换所有 stripInaccessible。务必理解它们的功能差异,并根据实际的业务需求和错误处理策略来选择最合适的方法。性能优化是在满足功能和可维护性前提下的锦上添花。

代码性能探针 SalesforceApex性能优化

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/8967