mcpField
P
performance-optimization

performance-optimization

github.com/happy-technologies-llc开发4143 次安装3 天前更新
代码仓库

关于 performance-optimization

这是一份面向 ServiceNow 开发者、管理员和性能工程师的性能优化技能,结合诊断流程与实践指导。内容涵盖查询、索引、脚本和客户端性能。

主要功能

  1. 01定位慢事务与慢查询
  2. 02优化 GlideRecord 查询
  3. 03避免 N+1 数据库调用
  4. 04检查并创建索引
  5. 05优化业务规则与客户端脚本

适用场景

  1. 01排查表单或列表加载缓慢
  2. 02缩短查询和事务耗时
  3. 03检查脚本中的重复数据库调用
  4. 04评估索引与业务规则瓶颈

MCP 相关

技能包含使用 SN-Query-Table、SN-Execute-Background-Script 等 ServiceNow MCP 工具进行性能分析和变更的示例。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill performance-optimization

安装后在 Claude Code 中输入/performance-optimization确认已加载。

详细介绍

ServiceNow 性能优化

概述

此技能提供了一套全面的方法,用于识别和解决 ServiceNow 中的性能问题:

  • 瓶颈识别: 使用事务日志、慢查询和会话调试
  • GlideRecord 优化: 高效数据库操作的最佳实践
  • 查询调优: 避免 N+1 查询、正确筛选并限制结果
  • 索引管理: 创建和维护有效的数据库索引
  • 缓存策略: 利用 ServiceNow 缓存机制
  • 客户端性能: 优化 UI 脚本和表单加载
  • 性能分析工具: 会话调试、事务配额和监控

适用场景: 表单加载缓慢、列表视图延迟、业务规则超时或系统整体响应迟缓时。

适用对象: 负责优化 ServiceNow 的开发者、管理员和性能工程师。

预期成果: 更快的表单加载速度、更短的事务耗时、更佳的用户体验,以及更低的系统资源消耗。

先决条件

  • 角色: admin(用于创建索引)、itil 或 developer(用于脚本分析)
  • 访问权限: sys_slow_transaction、syslog_transaction、sys_db_index 表
  • 知识要求: GlideRecord API、JavaScript、ServiceNow 架构
  • 工具: 会话调试功能、Stats.do 页面访问权限
  • 环境: 用于测试优化的预生产实例

性能指标基线

关键绩效指标(KPI)
指标目标警告严重
表单加载时间< 2 秒2-5 秒> 5 秒
列表视图加载时间< 3 秒3-7 秒> 7 秒
业务规则执行时间< 100 毫秒100-500 毫秒> 500 毫秒
客户端脚本执行时间< 200 毫秒200-500 毫秒> 500 毫秒
查询响应时间< 50 毫秒50-200 毫秒> 200 毫秒
API 响应时间< 1 秒1-3 秒> 3 秒

流程

阶段 1:性能分析和瓶颈识别
步骤 1.1:查询慢事务

找出实例中耗时最长的事务:

使用 MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sys_slow_transaction
  query: sys_created_on>javascript:gs.daysAgo(7)
  fields: url,response_time,user,client_transaction,sys_created_on
  limit: 50
  orderBy: response_time
  orderByDesc: true

分析结果:

  • 响应时间 > 10,000 毫秒表示存在严重问题
  • 查找模式:相同 URL、相同用户、相同时间段
  • client_transaction 值较高通常表示存在客户端问题
步骤 1.2:识别慢查询

找出耗时过长的数据库查询:

使用 MCP:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Query slow database operations
    var slowQueries = [];

    var gr = new GlideRecord('syslog_transaction');
    gr.addQuery('sys_created_on', '>', gs.daysAgo(1));
    gr.addQuery('type', 'sql');
    gr.orderByDesc('response_time');
    gr.setLimit(50);
    gr.query();

    while (gr.next()) {
      slowQueries.push({
        table: gr.url.toString().split(' ')[0],
        response_time: gr.response_time.toString(),
        query: gr.url.toString().substring(0, 200),
        created: gr.sys_created_on.toString()
      });
    }

    gs.info('Slow Queries Report:\n' + JSON.stringify(slowQueries, null, 2));
  description: Identify slow database queries
步骤 1.3:启用会话调试

进行详细的事务分析:

手动步骤(需要 UI):

  1. 导航到 System Diagnostics > Session Debug > Debug Business Rule
  2. 启用相关调试器:
    • Debug Business Rule (SQL)
    • Debug Log
    • Debug SQL (Detailed)
  3. 重现运行缓慢的操作
  4. 在 Debug Log 部分查看输出

查询调试输出:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: sourceLIKEDebug^sys_created_on>javascript:gs.minutesAgo(15)
  fields: message,source,sys_created_on
  limit: 100
步骤 1.4:事务配额监控

检查是否违反配额:

使用 MCP:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: messageLIKEquota^sys_created_on>javascript:gs.daysAgo(1)
  fields: message,source,sys_created_on
  limit: 50

常见配额违规:

配额默认限制症状
SQL 语句每个事务 1,000 条N+1 查询模式
脚本执行180 秒脚本运行时间过长
内存100 MB大量数据处理
出站 HTTP每个事务 10 次集成循环
阶段 2:GlideRecord 优化
步骤 2.1:正确使用 setLimit()

始终限制结果数量,以避免全表扫描:

// BAD: No limit - scans entire table
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();

// GOOD: Explicit limit
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(100);  // Only get what you need
gr.query();

不同限制值的适用场景:

使用场景建议限制值
在 UI 列表中显示20-50
批处理100-500
聚合输入最多 1000
检查是否存在1
步骤 2.2:使用 setFields() 选择字段

只获取所需字段:

// BAD: Gets all 100+ fields
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();

// GOOD: Gets only needed fields (70%+ faster)
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.setFields('sys_id,number,short_description,assigned_to');
gr.query();

使用 MCP:

Tool: SN-Query-Table
Parameters:
  table_name: incident
  query: active=true^priority=1
  fields: sys_id,number,short_description,assigned_to
  limit: 50
步骤 2.3:使用 getRowCount() 检查是否存在

进行简单的存在性检查时,避免遍历记录:

// BAD: Loads record just to check existence
var gr = new GlideRecord('incident');
gr.addQuery('number', 'INC0012345');
gr.query();
if (gr.next()) {
  // Exists
}

// GOOD: Use getRowCount() for existence
var gr = new GlideRecord('incident');
gr.addQuery('number', 'INC0012345');
gr.setLimit(1);
gr.query();
if (gr.getRowCount() > 0) {
  // Exists
}

// BEST: Use get() for single record by unique field
var gr = new GlideRecord('incident');
if (gr.get('number', 'INC0012345')) {
  // Exists and loaded
}
步骤 2.4:使用 GlideAggregate 进行计数

切勿通过遍历记录来计数:

// BAD: Loads all records just to count (can be 1000x slower)
var count = 0;
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();
while (gr.next()) {
  count++;
}

// GOOD: Use GlideAggregate
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
var count = 0;
if (ga.next()) {
  count = parseInt(ga.getAggregate('COUNT'));
}

高级聚合示例:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Get incident counts grouped by priority
    var stats = {};

    var ga = new GlideAggregate('incident');
    ga.addQuery('active', true);
    ga.addAggregate('COUNT');
    ga.groupBy('priority');
    ga.query();

    while (ga.next()) {
      var priority = ga.priority.getDisplayValue() || 'Unset';
      var count = parseInt(ga.getAggregate('COUNT'));
      stats[priority] = count;
    }

    gs.info('Incident Stats by Priority:\n' + JSON.stringify(stats, null, 2));
  description: Count incidents by priority using GlideAggregate
阶段 3:避免 N+1 查询
步骤 3.1:了解 N+1 问题

N+1 反模式:

// BAD: N+1 query pattern (1 + N database calls)
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  // This causes a separate query for EACH incident
  var user = new GlideRecord('sys_user');
  user.get(incidents.assigned_to);  // +1 query per iteration
  gs.info(incidents.number + ' - ' + user.name);
}
// Total queries: 1 + 100 = 101 queries!
步骤 3.2:解决方案 - 点式遍历

对引用字段使用点式遍历:

// GOOD: Single query with dot-walking
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  // Dot-walking retrieves reference data without extra queries
  var userName = incidents.assigned_to.name;
  var userEmail = incidents.assigned_to.email;
  gs.info(incidents.number + ' - ' + userName);
}
// Total queries: 1 query!
步骤 3.3:解决方案 - 批量加载

对于复杂场景,先加载所有引用:

// GOOD: Batch loading pattern
// Step 1: Collect all unique sys_ids
var userIds = [];
var incidents = new GlideRecord('incident');
incidents.addQuery('active', true);
incidents.addQuery('assigned_to', '!=', '');
incidents.setLimit(100);
incidents.query();

while (incidents.next()) {
  var userId = incidents.assigned_to.toString();
  if (userIds.indexOf(userId) === -1) {
    userIds.push(userId);
  }
}

// Step 2: Load all users in single query
var userMap = {};
if (userIds.length > 0) {
  var users = new GlideRecord('sys_user');
  users.addQuery('sys_id', 'IN', userIds.join(','));
  users.query();
  while (users.next()) {
    userMap[users.sys_id.toString()] = {
      name: users.name.toString(),
      email: users.email.toString()
    };
  }
}
// Total queries: 2 queries regardless of record count!
步骤 3.4:检测脚本

检测代码中的 N+1 模式:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Analyze business rules for potential N+1 patterns
    var suspects = [];

    var gr = new GlideRecord('sys_script');
    gr.addQuery('active', true);
    gr.addEncodedQuery('scriptLIKEwhile*next*^scriptLIKEnew GlideRecord');
    gr.setLimit(50);
    gr.query();

    while (gr.next()) {
      // Check for GlideRecord inside while loops
      var script = gr.script.toString();
      var lines = script.split('\n');
      var inWhileLoop = false;
      var hasInnerGR = false;

      for (var i = 0; i < lines.length; i++) {
        if (lines[i].indexOf('while') > -1 && lines[i].indexOf('.next()') > -1) {
          inWhileLoop = true;
        }
        if (inWhileLoop && lines[i].indexOf('new GlideRecord') > -1) {
          hasInnerGR = true;
          break;
        }
        if (inWhileLoop && lines[i].indexOf('}') > -1) {
          inWhileLoop = false;
        }
      }

      if (hasInnerGR) {
        suspects.push({
          name: gr.name.toString(),
          table: gr.collection.toString(),
          sys_id: gr.sys_id.toString()
        });
      }
    }

    gs.info('Potential N+1 Patterns Found: ' + suspects.length);
    gs.info(JSON.stringify(suspects, null, 2));
  description: Detect potential N+1 query patterns in business rules
阶段 4:索引优化
步骤 4.1:识别缺失的索引

查找没有索引但经常被查询的字段:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Analyze slow queries for missing indexes
    var fieldUsage = {};

    var gr = new GlideRecord('syslog_transaction');
    gr.addQuery('type', 'sql');
    gr.addQuery('sys_created_on', '>', gs.daysAgo(1));
    gr.addQuery('response_time', '>', 100);  // > 100ms
    gr.setLimit(500);
    gr.query();

    while (gr.next()) {
      var url = gr.url.toString();
      // Extract WHERE clause fields
      var whereMatch = url.match(/WHERE\s+(.+?)(?:ORDER|LIMIT|$)/i);
      if (whereMatch) {
        var conditions = whereMatch[1].split(/\s+AND\s+/i);
        conditions.forEach(function(cond) {
          var fieldMatch = cond.match(/^(\w+)/);
          if (fieldMatch) {
            var field = fieldMatch[1];
            fieldUsage[field] = (fieldUsage[field] || 0) + 1;
          }
        });
      }
    }

    // Sort by usage
    var sorted = Object.keys(fieldUsage).sort(function(a,b) {
      return fieldUsage[b] - fieldUsage[a];
    });

    gs.info('Most queried fields (potential index candidates):');
    sorted.slice(0, 20).forEach(function(field) {
      gs.info('  ' + field + ': ' + fieldUsage[field] + ' queries');
    });
  description: Identify index candidates from slow queries
步骤 4.2:检查现有索引

检查表上的当前索引:

Tool: SN-Query-Table
Parameters:
  table_name: sys_db_index
  query: table=incident
  fields: name,column_name,unique_index,active
  limit: 50
步骤 4.3:索引最佳实践

应建立索引的字段:

字段类型索引优先级示例
外键高assigned_to, caller_id
状态字段高state, active
日期筛选字段中opened_at, resolved_at
类别字段中category, subcategory
数字字段低number(通常会自动建立索引)

创建索引的指南:

  • 每张表最多建立 5-7 个索引(超过后会降低写入性能)
  • 对经常组合查询的字段使用复合索引
  • 避免为频繁更新的字段建立索引
  • 对大型表考虑使用部分索引

创建索引(需要管理员权限):

Tool: SN-Create-Record
Parameters:
  table_name: sys_db_index
  data:
    table: incident
    column_name: assignment_group
    active: true
阶段 5:异步业务规则
步骤 5.1:识别同步瓶颈

查找运行缓慢的同步业务规则:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script
  query: active=true^when!=async^collection=incident
  fields: name,when,order,sys_id
  limit: 50
步骤 5.2:转换为异步

适合转换为异步的条件:

可以异步处理必须保持同步
通知字段计算
日志记录/审计数据验证
外部集成工作流触发器
非关键更新安全检查
报告生成显示业务规则

异步业务规则模式:

// Async business rule example
// When: async
// Order: 100

(function executeRule(current, previous /*null when async*/) {

  // Safe for async - doesn't affect transaction
  var event = new GlideRecord('sysevent');
  event.initialize();
  event.name = 'incident.updated';
  event.parm1 = current.sys_id.toString();
  event.parm2 = current.number.toString();
  event.insert();

  // External notification
  var restMessage = new sn_ws.RESTMessageV2('External System', 'notify');
  restMessage.setStringParameter('incident_id', current.sys_id.toString());
  restMessage.executeAsync();  // Non-blocking

})(current, previous);
步骤 5.3:业务规则优化检查清单
// OPTIMIZED Business Rule Template
(function executeRule(current, previous) {

  // 1. Exit early if field hasn't changed
  if (previous && !current.state.changes()) {
    return;  // No work needed
  }

  // 2. Exit early for bulk operations
  if (current.operation() == 'update' && !gs.isInteractive()) {
    // Consider if this should run for imports/scripts
  }

  // 3. Use efficient existence checks
  if (!current.assigned_to.nil()) {
    // Only process if assigned
  }

  // 4. Cache repeated calculations
  var state = current.state.toString();  // Store once

  // 5. Avoid GlideRecord in loops
  // Use dot-walking or batch loading instead

  // 6. Set fields directly when possible
  current.work_notes = 'State changed to: ' + current.state.getDisplayValue();
  // Don't update() - let the transaction handle it

})(current, previous);
阶段 6:脚本性能模式
步骤 6.1:避免循环嵌套

反模式:

// BAD: O(n*m) complexity
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  var tasks = new GlideRecord('sc_task');
  tasks.query();
  while (tasks.next()) {
    // Extremely slow: checks every task for every incident
    if (tasks.request_item == incidents.sys_id) {
      // process
    }
  }
}

优化模式:

// GOOD: O(n+m) complexity
// Build lookup map first
var tasksByRequest = {};
var tasks = new GlideRecord('sc_task');
tasks.query();
while (tasks.next()) {
  var reqId = tasks.request_item.toString();
  if (!tasksByRequest[reqId]) {
    tasksByRequest[reqId] = [];
  }
  tasksByRequest[reqId].push(tasks.sys_id.toString());
}

// Now iterate incidents with O(1) lookups
var incidents = new GlideRecord('incident');
incidents.query();
while (incidents.next()) {
  var relatedTasks = tasksByRequest[incidents.sys_id.toString()] || [];
  // process related tasks
}
步骤 6.2:字符串拼接

避免重复拼接字符串:

// BAD: Creates new string object each iteration
var result = '';
var gr = new GlideRecord('incident');
gr.setLimit(1000);
gr.query();
while (gr.next()) {
  result += gr.number + ', ';  // Memory inefficient
}

// GOOD: Use array join
var results = [];
var gr = new GlideRecord('incident');
gr.setLimit(1000);
gr.query();
while (gr.next()) {
  results.push(gr.number.toString());
}
var result = results.join(', ');
步骤 6.3:高效处理 JSON

高效处理大型 JSON:

// BAD: Parse entire JSON into memory
var bigJson = gr.large_json_field.toString();
var data = JSON.parse(bigJson);  // Entire object in memory

// GOOD: Stream processing for large data
// Use GlideStringUtil for validation
var jsonField = gr.large_json_field.toString();
if (GlideStringUtil.isJSON(jsonField)) {
  // Process in chunks if possible
  // Or use GlideElement methods
}

// BEST: Store structured data in related records
// instead of large JSON blobs
阶段 7:客户端性能
步骤 7.1:客户端脚本优化

高效的客户端脚本:

// GOOD: Client script with performance optimizations
function onChange(control, oldValue, newValue, isLoading) {
  // 1. Skip during form load
  if (isLoading) {
    return;
  }

  // 2. Skip if value unchanged
  if (oldValue == newValue) {
    return;
  }

  // 3. Use g_form caching
  var priority = g_form.getValue('priority');  // Cached locally

  // 4. Batch field updates
  g_form.setReadOnly('resolution_code', true);
  g_form.setMandatory('resolution_notes', true);
  // Multiple setX calls are batched automatically

  // 5. Avoid synchronous GlideAjax
  var ga = new GlideAjax('IncidentUtils');
  ga.addParam('sysparm_name', 'getDefaultAssignee');
  ga.addParam('sysparm_priority', priority);
  ga.getXMLAnswer(function(answer) {
    // Async callback - doesn't block UI
    if (answer) {
      g_form.setValue('assigned_to', answer);
    }
  });
}
步骤 7.2:UI 策略与客户端脚本

选择合适的工具:

使用场景推荐方案原因
显示/隐藏字段UI 策略声明式,速度更快
设置必填项UI 策略无 JavaScript 开销
简单条件UI 策略已缓存,无需网络请求
复杂逻辑客户端脚本灵活性更高
GlideAjax 调用客户端脚本获取服务器数据所必需
字段计算客户端脚本动态计算
步骤 7.3:缩短表单加载时间

表单性能检查清单:

  • 默认将相关列表限制为 5 条记录
  • 谨慎使用显示业务规则
  • 尽可能避免使用 onLoad 脚本
  • 移除未使用的表单分区
  • 延迟加载嵌入式列表

查询加载缓慢的表单:

Tool: SN-Query-Table
Parameters:
  table_name: sys_slow_transaction
  query: urlLIKEform^sys_created_on>javascript:gs.daysAgo(7)
  fields: url,response_time,user
  limit: 50
  orderByDesc: response_time
阶段 8:缓存策略
步骤 8.1:系统属性缓存

使用带缓存的 gs.getProperty:

// Properties are cached automatically
var maxRecords = gs.getProperty('custom.max_records', '100');

// For frequently accessed properties, consider Script Include caching
var CachedConfig = Class.create();
CachedConfig.prototype = {
  initialize: function() {
    this.cache = {};
  },

  get: function(name, defaultValue) {
    if (!this.cache[name]) {
      this.cache[name] = gs.getProperty(name, defaultValue);
    }
    return this.cache[name];
  },

  type: 'CachedConfig'
};
步骤 8.2:GlideSystem 缓存

利用内置缓存:

// Cache expensive calculations
var cacheKey = 'user_permissions_' + gs.getUserID();
var cached = gs.getSession().getClientData(cacheKey);

if (!cached) {
  // Calculate permissions (expensive)
  var permissions = calculateUserPermissions();
  gs.getSession().putClientData(cacheKey, JSON.stringify(permissions));
  cached = JSON.stringify(permissions);
}

var userPermissions = JSON.parse(cached);
步骤 8.3:引用限定符缓存

优化引用限定符:

// BAD: Expensive query runs every time
javascript:new MyUtil().getValidAssignees()

// GOOD: Use encoded query directly
active=true^roles=itil

// BEST: Cache complex qualifiers
javascript:gs.getProperty('incident.assignee_qualifier', 'active=true')
阶段 9:性能监控
步骤 9.1:创建性能仪表板查询

监控关键指标:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Performance Health Check
    var report = {
      timestamp: new GlideDateTime().getDisplayValue(),
      metrics: {}
    };

    // 1. Slow transactions today
    var slowTx = new GlideAggregate('sys_slow_transaction');
    slowTx.addQuery('sys_created_on', '>', gs.beginningOfToday());
    slowTx.addAggregate('COUNT');
    slowTx.addAggregate('AVG', 'response_time');
    slowTx.query();
    if (slowTx.next()) {
      report.metrics.slow_transactions = {
        count: parseInt(slowTx.getAggregate('COUNT')),
        avg_response_time: Math.round(parseFloat(slowTx.getAggregate('AVG', 'response_time')))
      };
    }

    // 2. Business rule execution times
    var brLogs = new GlideAggregate('syslog_transaction');
    brLogs.addQuery('type', 'business rule');
    brLogs.addQuery('sys_created_on', '>', gs.beginningOfToday());
    brLogs.addAggregate('COUNT');
    brLogs.addAggregate('AVG', 'response_time');
    brLogs.query();
    if (brLogs.next()) {
      report.metrics.business_rules = {
        count: parseInt(brLogs.getAggregate('COUNT')),
        avg_execution_time: Math.round(parseFloat(brLogs.getAggregate('AVG', 'response_time')))
      };
    }

    // 3. Error rate
    var errors = new GlideAggregate('syslog');
    errors.addQuery('level', '2');  // Error level
    errors.addQuery('sys_created_on', '>', gs.beginningOfToday());
    errors.addAggregate('COUNT');
    errors.query();
    if (errors.next()) {
      report.metrics.errors_today = parseInt(errors.getAggregate('COUNT'));
    }

    // 4. Active scheduled jobs
    var jobs = new GlideAggregate('sys_trigger');
    jobs.addQuery('state', '0');  // Ready
    jobs.addAggregate('COUNT');
    jobs.query();
    if (jobs.next()) {
      report.metrics.pending_jobs = parseInt(jobs.getAggregate('COUNT'));
    }

    gs.info('Performance Report:\n' + JSON.stringify(report, null, 2));
  description: Generate performance health report
步骤 9.2:设置告警

创建配额违规告警:

Tool: SN-Create-Record
Parameters:
  table_name: sysevent_script_action
  data:
    name: Performance Quota Alert
    event_name: performance.quota.exceeded
    script: |
      // Send alert on quota violations
      var quota_type = event.parm1;
      var details = event.parm2;

      gs.eventQueue('custom.performance.alert', null, quota_type, details);

工具使用摘要

操作MCP 工具用途
查询慢事务SN-Query-Table查找性能瓶颈
分析查询SN-Execute-Background-Script深入分析脚本
检查架构SN-Get-Table-Schema验证字段类型
发现索引SN-Discover-Table-Schema查找现有索引
创建索引SN-Create-Record添加缺失的索引

性能优化清单

GlideRecord
  • 始终使用 setLimit()
  • 使用 setFields() 或 fields 参数,仅选择所需字段
  • 使用 GlideAggregate 进行计数
  • 使用 get() 查询单条记录
  • 避免在循环中使用 GlideRecord(N+1)
业务规则
  • 使用 .changes() 进行条件执行
  • 将非关键规则转换为异步规则
  • 合理设置规则顺序(先验证)
  • 尽可能提前退出
查询
  • 为经常用于筛选的字段创建索引
  • 对复杂条件使用编码查询
  • 避免对大型文本字段使用 LIKE 查询
  • 限制日期范围查询
客户端
  • 尽可能优先使用 UI Policies,而非 Client Scripts
  • 以异步方式调用 GlideAjax
  • 表单加载期间跳过 onChange
  • 尽量减少相关列表中的记录
缓存
  • 使用系统属性进行配置
  • 缓存开销大的计算结果
  • 适当利用会话存储

故障排查

表单加载时间过长

症状: 表单加载时间超过 5 秒 原因:

  • 相关列表过多
  • 显示业务规则开销过大
  • 带脚本的 UI Policies 过于复杂
  • 附件预览过大

解决方案:

Tool: SN-Query-Table
Parameters:
  table_name: sys_ui_related_list
  query: table=incident
  fields: related_list,order,view
  limit: 50

检查并减少相关列表,调低默认计数。

业务规则超时

症状: 事务在 180 秒后超时 原因:

  • 无限制的 GlideRecord 查询
  • N+1 查询模式
  • 同步规则中调用外部 API

解决方案:

  1. 为所有查询添加 setLimit()
  2. 使用批量加载模式
  3. 将规则转换为异步业务规则
  4. 将集成移至计划作业
超出内存配额

症状: 出现“Memory quota exceeded”错误 原因:

  • 加载的记录过多
  • 字符串拼接内容过大
  • 解析大型 JSON/XML

解决方案:

// Process in batches
var BATCH_SIZE = 200;
var offset = 0;
var hasMore = true;

while (hasMore) {
  var gr = new GlideRecord('incident');
  gr.addQuery('active', true);
  gr.orderBy('sys_created_on');
  gr.chooseWindow(offset, offset + BATCH_SIZE);
  gr.query();

  var count = 0;
  while (gr.next()) {
    // Process record
    count++;
  }

  hasMore = (count == BATCH_SIZE);
  offset += BATCH_SIZE;

  // Force garbage collection hint
  gs.nil();
}
列表视图速度慢

症状: 列表视图加载时间超过 7 秒 原因:

  • 筛选字段缺少索引
  • ACL 中的脚本条件过于复杂
  • 对大量引用字段解析显示值

解决方案:

  1. 为经常用于筛选的列添加索引
  2. 简化 ACL 条件
  3. 减少可见列
  4. 使用 list v3 提升性能

最佳实践

实践影响难度
添加 setLimit()高容易
使用 setFields()高容易
修复 N+1 查询严重中等
添加索引高中等
转换为异步中等中等
优化循环高中等
启用缓存中等中等
客户端脚本异步化中等容易

相关技能

  • admin/script-execution - 后台脚本执行模式
  • admin/batch-operations - 高效的批量记录操作
  • admin/schema-discovery - 了解表结构
  • development/debugging-techniques - 脚本故障排查

参考资料

评论

支持 Markdown