关于 performance-optimization
这是一份面向 ServiceNow 开发者、管理员和性能工程师的性能优化技能,结合诊断流程与实践指导。内容涵盖查询、索引、脚本和客户端性能。
主要功能
- 01定位慢事务与慢查询
- 02优化 GlideRecord 查询
- 03避免 N+1 数据库调用
- 04检查并创建索引
- 05优化业务规则与客户端脚本
适用场景
- 01排查表单或列表加载缓慢
- 02缩短查询和事务耗时
- 03检查脚本中的重复数据库调用
- 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):
- 导航到 System Diagnostics > Session Debug > Debug Business Rule
- 启用相关调试器:
- Debug Business Rule (SQL)
- Debug Log
- Debug SQL (Detailed)
- 重现运行缓慢的操作
- 在 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
解决方案:
- 为所有查询添加 setLimit()
- 使用批量加载模式
- 将规则转换为异步业务规则
- 将集成移至计划作业
超出内存配额
症状: 出现“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 中的脚本条件过于复杂
- 对大量引用字段解析显示值
解决方案:
- 为经常用于筛选的列添加索引
- 简化 ACL 条件
- 减少可见列
- 使用 list v3 提升性能
最佳实践
| 实践 | 影响 | 难度 |
|---|---|---|
| 添加 setLimit() | 高 | 容易 |
| 使用 setFields() | 高 | 容易 |
| 修复 N+1 查询 | 严重 | 中等 |
| 添加索引 | 高 | 中等 |
| 转换为异步 | 中等 | 中等 |
| 优化循环 | 高 | 中等 |
| 启用缓存 | 中等 | 中等 |
| 客户端脚本异步化 | 中等 | 容易 |
相关技能
admin/script-execution- 后台脚本执行模式admin/batch-operations- 高效的批量记录操作admin/schema-discovery- 了解表结构development/debugging-techniques- 脚本故障排查