mcpField
B
batch-operations

batch-operations

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

关于 batch-operations

面向处理批量记录创建、更新和导入的 ServiceNow 管理员、开发者及数据迁移人员。内容涵盖分批处理、性能优化、错误跟踪、重试和回滚,并使用 ServiceNow 工具与脚本举例。

主要功能

  1. 01批量创建和更新记录
  2. 02创建关联记录的模式
  3. 03分批处理与查询优化
  4. 04逐条错误跟踪和重试
  5. 05回滚与数据迁移模式

适用场景

  1. 01批量创建或更新 ServiceNow 记录
  2. 02在表之间迁移数据
  3. 03导入外部 JSON 数据
  4. 04跟踪错误并批量修正记录

MCP 相关

介绍使用 SN-Batch-Create、SN-Batch-Update、SN-Create-Record 和 SN-Execute-Background-Script 等 MCP 工具执行批量及并行操作。

安装

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

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

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

详细介绍

批量操作

概述

本技能介绍在 ServiceNow 中执行批量操作的高效方法:

  • 创建大量记录并正确维护关系
  • 批量更新并进行验证和错误处理
  • 大型数据集的性能注意事项
  • 事务管理和回滚策略
  • 并行处理模式

适用场景: 一次创建或更新大量记录、数据迁移、批量导入或大规模数据更正。

适用人群: 管理员、开发者和数据迁移专家。

前提条件

  • 角色: admin 或表特定的写入权限
  • 访问权限: 目标表和相关引用表
  • 知识: GlideRecord API、表关系、ServiceNow 数据模型
  • 环境: 对于大型操作,先在预生产环境中测试

性能指南

记录数量建议
数量方法预计耗时
1-10并行 MCP 调用< 5 秒
10-100SN-Batch-Create/Update5-30 秒
100-1,000后台脚本分批处理1-5 分钟
1,000-10,000通过批处理运行计划作业10-60 分钟
10,000+导入集或数据泵数小时
MCP 并行处理

已测试并验证: 在单条消息中成功执行 43+ 个并行 MCP 调用。

Parallel MCP calls are preferred for:
- Independent record operations
- Different tables
- No interdependencies
- Maximum throughput

操作步骤

阶段 1:批量创建记录
步骤 1.1:简单批量创建

通过单次操作创建多条记录。

使用 MCP:

Tool: SN-Batch-Create
Parameters:
  records:
    - table_name: incident
      data:
        short_description: "Server down - Web01"
        caller_id: [user_sys_id]
        category: hardware
        priority: 1
    - table_name: incident
      data:
        short_description: "Server down - Web02"
        caller_id: [user_sys_id]
        category: hardware
        priority: 1
    - table_name: incident
      data:
        short_description: "Server down - Web03"
        caller_id: [user_sys_id]
        category: hardware
        priority: 1

响应:

{
  "success": true,
  "created": [
    { "table": "incident", "sys_id": "abc123...", "number": "INC0010001" },
    { "table": "incident", "sys_id": "def456...", "number": "INC0010002" },
    { "table": "incident", "sys_id": "ghi789...", "number": "INC0010003" }
  ],
  "errors": []
}
步骤 1.2:并行 MCP 调用

如需最大吞吐量,请在单条消息中使用并行 MCP 调用。

并行创建事件记录(10 条):

# Call 1:
Tool: SN-Create-Record
Parameters:
  table_name: incident
  data:
    short_description: "Batch test 1"
    category: inquiry

# Call 2:
Tool: SN-Create-Record
Parameters:
  table_name: incident
  data:
    short_description: "Batch test 2"
    category: inquiry

# Call 3-10: Continue pattern...

所有调用会同时执行,操作之间无需等待。

步骤 1.3:创建并维护关系

创建父记录和子记录,同时维护两者之间的关系。

使用 MCP(依赖项需按顺序处理):

# Step 1: Create parent record
Tool: SN-Create-Record
Parameters:
  table_name: change_request
  data:
    short_description: "Server Infrastructure Upgrade"
    category: hardware
    type: standard

# Step 2: Create child tasks (use parent sys_id from step 1)
Tool: SN-Batch-Create
Parameters:
  records:
    - table_name: change_task
      data:
        parent: [change_request_sys_id]
        short_description: "Backup existing configuration"
        change_task_type: planning
        order: 100
    - table_name: change_task
      data:
        parent: [change_request_sys_id]
        short_description: "Install new hardware"
        change_task_type: implementation
        order: 200
    - table_name: change_task
      data:
        parent: [change_request_sys_id]
        short_description: "Verify functionality"
        change_task_type: testing
        order: 300
步骤 1.4:使用后台脚本进行复杂创建

对于复杂逻辑或超大批次:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Batch creation with relationships
    var projectData = [
      {
        name: 'Project Alpha',
        tasks: [
          { name: 'Task A1', order: 1 },
          { name: 'Task A2', order: 2 },
          { name: 'Task A3', order: 3 }
        ]
      },
      {
        name: 'Project Beta',
        tasks: [
          { name: 'Task B1', order: 1 },
          { name: 'Task B2', order: 2 }
        ]
      }
    ];

    var created = { projects: 0, tasks: 0 };

    projectData.forEach(function(project) {
      // Create project
      var prj = new GlideRecord('pm_project');
      prj.initialize();
      prj.short_description = project.name;
      var prjId = prj.insert();
      created.projects++;

      // Create tasks
      project.tasks.forEach(function(task) {
        var tsk = new GlideRecord('pm_project_task');
        tsk.initialize();
        tsk.parent = prjId;
        tsk.short_description = task.name;
        tsk.order = task.order;
        tsk.insert();
        created.tasks++;
      });

      gs.info('Created project: ' + project.name + ' with ' + project.tasks.length + ' tasks');
    });

    gs.info('Summary: ' + JSON.stringify(created));
  description: Batch create projects with tasks
阶段 2:批量更新
步骤 2.1:简单批量更新

通过 sys_id 更新多条记录。

使用 MCP:

Tool: SN-Batch-Update
Parameters:
  updates:
    - table_name: incident
      sys_id: [incident1_sys_id]
      data:
        priority: 2
        state: 2
        work_notes: "Escalated per management request"
    - table_name: incident
      sys_id: [incident2_sys_id]
      data:
        priority: 2
        state: 2
        work_notes: "Escalated per management request"
    - table_name: incident
      sys_id: [incident3_sys_id]
      data:
        priority: 2
        state: 2
        work_notes: "Escalated per management request"
步骤 2.2:基于查询的批量更新

更新所有符合查询条件的记录。

使用后台脚本:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Query-based bulk update
    var TABLE = 'incident';
    var QUERY = 'active=true^category=inquiry^priority=5';
    var UPDATES = {
      priority: 4,
      work_notes: 'Priority adjusted per SLA review'
    };
    var MAX_UPDATES = 1000;
    var DRY_RUN = true;

    var gr = new GlideRecord(TABLE);
    gr.addEncodedQuery(QUERY);
    gr.setLimit(MAX_UPDATES);
    gr.query();

    gs.info('Found ' + gr.getRowCount() + ' records matching query');

    var updated = 0;
    var errors = 0;

    while (gr.next()) {
      try {
        if (DRY_RUN) {
          gs.info('[DRY RUN] Would update: ' + gr.number);
        } else {
          for (var field in UPDATES) {
            gr[field] = UPDATES[field];
          }
          gr.update();
          updated++;
        }
      } catch (e) {
        errors++;
        gs.error('Error updating ' + gr.number + ': ' + e.message);
      }
    }

    gs.info('Complete: Updated=' + updated + ', Errors=' + errors + ', DryRun=' + DRY_RUN);
  description: Bulk update incidents by query
步骤 2.3:条件更新

根据记录条件应用不同的更新。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Conditional bulk updates
    var gr = new GlideRecord('incident');
    gr.addQuery('active', true);
    gr.addQuery('priority', 'IN', '1,2');
    gr.setLimit(500);
    gr.query();

    var stats = { p1_updates: 0, p2_updates: 0 };

    while (gr.next()) {
      if (gr.priority == 1) {
        // P1 gets immediate attention
        if (gr.assigned_to.nil()) {
          gr.assignment_group = 'Critical Response Team';
          gr.work_notes = 'Auto-assigned to Critical Response Team';
          gr.update();
          stats.p1_updates++;
        }
      } else if (gr.priority == 2) {
        // P2 gets SLA check
        var age = GlideDateTime.subtract(new GlideDateTime(gr.sys_created_on), new GlideDateTime()).getDayPart();
        if (age > 2 && gr.state == 1) {
          gr.escalation = 1;
          gr.work_notes = 'Auto-escalated: P2 older than 2 days without progress';
          gr.update();
          stats.p2_updates++;
        }
      }
    }

    gs.info('Conditional updates complete: ' + JSON.stringify(stats));
  description: Apply conditional updates based on priority
阶段 3:性能优化
步骤 3.1:批处理模式

将大型数据集分成易于管理的批次进行处理。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Optimized batch processing
    var CONFIG = {
      table: 'incident',
      query: 'active=true',
      batchSize: 200,
      maxBatches: 50,
      pauseBetweenBatches: false  // true adds 100ms delay
    };

    var totalProcessed = 0;
    var batchNum = 0;
    var startTime = new GlideDateTime();

    while (batchNum < CONFIG.maxBatches) {
      var gr = new GlideRecord(CONFIG.table);
      gr.addEncodedQuery(CONFIG.query);
      gr.orderBy('sys_created_on');
      gr.chooseWindow(batchNum * CONFIG.batchSize, (batchNum + 1) * CONFIG.batchSize);
      gr.query();

      if (!gr.hasNext()) {
        break;
      }

      var batchProcessed = 0;
      while (gr.next()) {
        // Process record (minimal operations for speed)
        batchProcessed++;
      }

      totalProcessed += batchProcessed;
      batchNum++;

      // Progress logging every 10 batches
      if (batchNum % 10 === 0) {
        gs.info('Progress: Batch ' + batchNum + ', Total processed: ' + totalProcessed);
      }

      // Optional pause to reduce system load
      if (CONFIG.pauseBetweenBatches) {
        gs.sleep(100);
      }
    }

    var endTime = new GlideDateTime();
    var duration = GlideDateTime.subtract(startTime, endTime).getNumericValue() / 1000;
    var rate = totalProcessed / duration;

    gs.info('Complete: ' + totalProcessed + ' records in ' + duration.toFixed(2) + ' seconds (' + rate.toFixed(0) + ' records/sec)');
  description: Optimized batch processing with metrics
步骤 3.2:优化查询

在批量操作中使用高效的查询模式。

使用 GlideAggregate 进行计数:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Efficient counting with GlideAggregate
    var ga = new GlideAggregate('incident');
    ga.addQuery('active', true);
    ga.addAggregate('COUNT');
    ga.groupBy('priority');
    ga.query();

    var results = [];
    while (ga.next()) {
      results.push({
        priority: ga.priority.getDisplayValue(),
        count: parseInt(ga.getAggregate('COUNT'))
      });
    }

    gs.info('Priority Distribution:\n' + JSON.stringify(results, null, 2));
  description: Count incidents by priority efficiently

批量更新时使用 setWorkflow(false):

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Disable business rules for bulk operations
    var gr = new GlideRecord('incident');
    gr.addQuery('state', 7);  // Closed
    gr.addQuery('sys_updated_on', '<', gs.daysAgo(365));
    gr.setLimit(1000);
    gr.query();

    // Disable workflows/business rules
    gr.setWorkflow(false);

    var count = 0;
    while (gr.next()) {
      gr.archived = true;
      gr.update();
      count++;
    }

    gs.info('Archived ' + count + ' old closed incidents (business rules disabled)');
  description: Bulk archive with business rules disabled

警告: 只有在充分了解其影响时,才能禁用工作流。

步骤 3.3:内存优化

避免大型数据集导致内存问题。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Memory-efficient processing
    var BATCH_SIZE = 100;
    var offset = 0;
    var totalProcessed = 0;
    var hasMore = true;

    while (hasMore) {
      // Use GlideRecord with window to avoid loading all records
      var gr = new GlideRecord('cmdb_ci');
      gr.addQuery('operational_status', 1);
      gr.orderBy('sys_id');  // Consistent ordering
      gr.chooseWindow(offset, offset + BATCH_SIZE);
      gr.query();

      if (gr.getRowCount() === 0) {
        hasMore = false;
        break;
      }

      while (gr.next()) {
        // Process record - avoid storing in arrays
        // Direct operations only
        totalProcessed++;
      }

      offset += BATCH_SIZE;

      // Check if we got fewer than batch size (last batch)
      if (gr.getRowCount() < BATCH_SIZE) {
        hasMore = false;
      }
    }

    gs.info('Processed ' + totalProcessed + ' CI records');
  description: Memory-efficient CI processing
阶段 4:错误处理
步骤 4.1:单条记录错误处理

分别跟踪成功和失败的记录。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Comprehensive error tracking
    var results = {
      success: [],
      errors: [],
      skipped: []
    };

    var gr = new GlideRecord('incident');
    gr.addQuery('active', true);
    gr.setLimit(100);
    gr.query();

    while (gr.next()) {
      try {
        // Validation
        if (gr.short_description.nil()) {
          results.skipped.push({
            number: gr.number.toString(),
            reason: 'Missing short description'
          });
          continue;
        }

        // Attempt update
        gr.work_notes = 'Batch processed on ' + new GlideDateTime().getDisplayValue();
        gr.update();

        results.success.push({
          number: gr.number.toString(),
          sys_id: gr.sys_id.toString()
        });

      } catch (e) {
        results.errors.push({
          number: gr.number.toString(),
          error: e.message
        });
      }
    }

    gs.info('Results Summary:');
    gs.info('  Success: ' + results.success.length);
    gs.info('  Errors: ' + results.errors.length);
    gs.info('  Skipped: ' + results.skipped.length);

    if (results.errors.length > 0) {
      gs.error('Errors:\n' + JSON.stringify(results.errors, null, 2));
    }

    if (results.skipped.length > 0) {
      gs.warn('Skipped:\n' + JSON.stringify(results.skipped, null, 2));
    }
  description: Batch update with comprehensive error tracking
步骤 4.2:重试模式

为瞬时故障实现重试逻辑。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Retry pattern for batch operations
    var MAX_RETRIES = 3;
    var RETRY_DELAY = 1000;  // milliseconds

    function updateWithRetry(tableName, sysId, data) {
      var attempts = 0;
      var lastError = null;

      while (attempts < MAX_RETRIES) {
        attempts++;
        try {
          var gr = new GlideRecord(tableName);
          if (gr.get(sysId)) {
            for (var field in data) {
              gr[field] = data[field];
            }
            gr.update();
            return { success: true, attempts: attempts };
          } else {
            return { success: false, error: 'Record not found', attempts: attempts };
          }
        } catch (e) {
          lastError = e.message;
          gs.warn('Attempt ' + attempts + ' failed: ' + e.message);
          if (attempts < MAX_RETRIES) {
            gs.sleep(RETRY_DELAY * attempts);  // Exponential backoff
          }
        }
      }

      return { success: false, error: lastError, attempts: attempts };
    }

    // Example usage
    var recordsToUpdate = [
      { sys_id: '[sys_id_1]', data: { state: 2 } },
      { sys_id: '[sys_id_2]', data: { state: 2 } }
    ];

    var results = [];
    recordsToUpdate.forEach(function(record) {
      var result = updateWithRetry('incident', record.sys_id, record.data);
      result.sys_id = record.sys_id;
      results.push(result);
    });

    gs.info('Update Results:\n' + JSON.stringify(results, null, 2));
  description: Batch update with retry logic
步骤 4.3:事务回滚

为失败的批量操作实现回滚。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Batch operation with rollback capability
    var operations = [];  // Track for potential rollback
    var allSuccess = true;

    try {
      // Operation 1
      var inc1 = new GlideRecord('incident');
      inc1.get('[incident_1_sys_id]');
      var oldState1 = inc1.state.toString();
      inc1.state = 6;
      inc1.update();
      operations.push({ table: 'incident', sys_id: inc1.sys_id.toString(), field: 'state', oldValue: oldState1 });

      // Operation 2
      var inc2 = new GlideRecord('incident');
      inc2.get('[incident_2_sys_id]');
      var oldState2 = inc2.state.toString();
      inc2.state = 6;
      inc2.update();
      operations.push({ table: 'incident', sys_id: inc2.sys_id.toString(), field: 'state', oldValue: oldState2 });

      // Operation 3 (simulated failure)
      // throw new Error('Simulated failure for testing rollback');

      gs.info('All ' + operations.length + ' operations completed successfully');

    } catch (e) {
      allSuccess = false;
      gs.error('Error occurred: ' + e.message);
      gs.info('Rolling back ' + operations.length + ' operations...');

      // Rollback in reverse order
      for (var i = operations.length - 1; i >= 0; i--) {
        var op = operations[i];
        try {
          var rollback = new GlideRecord(op.table);
          if (rollback.get(op.sys_id)) {
            rollback[op.field] = op.oldValue;
            rollback.setWorkflow(false);  // Avoid triggering additional logic
            rollback.update();
            gs.info('Rolled back: ' + op.table + ' ' + op.sys_id);
          }
        } catch (rollbackError) {
          gs.error('Rollback failed for ' + op.sys_id + ': ' + rollbackError.message);
        }
      }

      gs.info('Rollback complete');
    }
  description: Batch operation with transaction-like rollback
阶段 5:数据迁移模式
步骤 5.1:表间迁移

通过转换将数据从一个表迁移到另一个表。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Table-to-table migration
    var SOURCE_TABLE = 'x_old_app_requests';
    var TARGET_TABLE = 'sc_request';
    var BATCH_SIZE = 100;

    var fieldMapping = {
      'old_number': 'number',
      'old_description': 'description',
      'requestor': 'requested_for',
      'submit_date': 'opened_at'
    };

    var migrated = 0;
    var errors = 0;

    var source = new GlideRecord(SOURCE_TABLE);
    source.addQuery('migrated', false);
    source.setLimit(BATCH_SIZE);
    source.query();

    while (source.next()) {
      try {
        var target = new GlideRecord(TARGET_TABLE);
        target.initialize();

        // Apply field mapping
        for (var oldField in fieldMapping) {
          var newField = fieldMapping[oldField];
          target[newField] = source[oldField];
        }

        // Transform values
        target.state = mapState(source.old_state);
        target.priority = mapPriority(source.old_priority);

        var newSysId = target.insert();

        // Mark source as migrated
        source.migrated = true;
        source.migrated_to = newSysId;
        source.update();

        migrated++;

      } catch (e) {
        errors++;
        gs.error('Error migrating ' + source.old_number + ': ' + e.message);
      }
    }

    gs.info('Migration complete: ' + migrated + ' migrated, ' + errors + ' errors');

    function mapState(oldState) {
      var stateMap = { 'open': 1, 'in_progress': 2, 'closed': 3 };
      return stateMap[oldState] || 1;
    }

    function mapPriority(oldPriority) {
      var priorityMap = { 'critical': 1, 'high': 2, 'medium': 3, 'low': 4 };
      return priorityMap[oldPriority] || 4;
    }
  description: Table-to-table data migration with transformation
步骤 5.2:外部数据导入

从外部来源导入数据(JSON 数组)。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // External data import pattern
    var importData = [
      { email: '[email protected]', first_name: 'John', last_name: 'Doe', department: 'IT' },
      { email: '[email protected]', first_name: 'Jane', last_name: 'Smith', department: 'HR' },
      { email: '[email protected]', first_name: 'Bob', last_name: 'Wilson', department: 'Finance' }
    ];

    var results = { created: 0, updated: 0, errors: 0 };

    importData.forEach(function(data) {
      try {
        // Check for existing record
        var existing = new GlideRecord('sys_user');
        existing.addQuery('email', data.email);
        existing.query();

        if (existing.next()) {
          // Update existing
          existing.first_name = data.first_name;
          existing.last_name = data.last_name;
          existing.department = lookupDepartment(data.department);
          existing.update();
          results.updated++;
          gs.info('Updated user: ' + data.email);
        } else {
          // Create new
          var newUser = new GlideRecord('sys_user');
          newUser.initialize();
          newUser.email = data.email;
          newUser.user_name = data.email.split('@')[0];
          newUser.first_name = data.first_name;
          newUser.last_name = data.last_name;
          newUser.department = lookupDepartment(data.department);
          newUser.active = true;
          newUser.insert();
          results.created++;
          gs.info('Created user: ' + data.email);
        }

      } catch (e) {
        results.errors++;
        gs.error('Error importing ' + data.email + ': ' + e.message);
      }
    });

    gs.info('Import complete: ' + JSON.stringify(results));

    function lookupDepartment(name) {
      var dept = new GlideRecord('cmn_department');
      dept.addQuery('name', name);
      dept.query();
      if (dept.next()) {
        return dept.sys_id.toString();
      }
      return '';
    }
  description: Import external user data with upsert logic

工具用法摘要

操作MCP 工具用途
批量创建SN-Batch-Create创建多条记录
批量更新SN-Batch-Update通过 sys_id 更新多条记录
并行创建SN-Create-Record(多个)最大吞吐量
并行更新SN-Update-Record(多个)最大吞吐量
复杂批处理SN-Execute-Background-Script高级逻辑
查询SN-Query-Table查找用于批量操作的记录

性能比较

方法100 条记录1,000 条记录10,000 条记录
串行 MCP 调用约 50 秒约 500 秒不切实际
并行 MCP(10 个)约 5 秒约 50 秒约 500 秒
SN-Batch-Create约 3 秒约 30 秒约 300 秒
后台脚本约 2 秒约 20 秒约 200 秒
setWorkflow(false)约 1 秒约 10 秒约 100 秒

最佳实践

  • 始终先测试: 使用 DRY_RUN=true 运行,或先在小范围内运行
  • 设置限制: 切勿处理不限数量的记录
  • 合理分批: 每批处理 100-500 条记录,以获得最佳性能
  • 记录进度: 每执行 10-100 次操作报告一次进度
  • 处理错误: 为每条记录实现 try-catch
  • 跟踪更改: 保存旧值,以便在需要时回滚
  • 考虑业务规则: 仅在适当情况下使用 setWorkflow(false)
  • 监控性能: 跟踪处理速率(条记录/秒)
  • 使用事务: 将相关更改分组,以实现原子操作
  • 记录操作: 记录更改内容,以供审计

故障排除

操作超时

症状: 脚本执行在中途停止 原因:

  • 单次执行中的记录过多
  • 触发了复杂的业务规则 解决方案:
  • 减小批次大小
  • 使用自调度模式
  • 如适用,禁用工作流
内存错误

症状: 出现“内存不足”或性能缓慢 原因:

  • 一次加载了过多记录
  • 将结果存储在数组中 解决方案:
  • 使用 chooseWindow() 分页
  • 处理后立即丢弃
  • 避免在内存中累积结果
创建了重复记录

症状: 同一记录有多个副本 原因:

  • 未检查唯一约束
  • 脚本运行了多次 解决方案:
  • 插入前始终检查记录是否已存在
  • 使用唯一键去重
  • 添加幂等性检查
业务规则未触发

症状: 预期的副作用未发生 原因:

  • 正在使用 setWorkflow(false)
  • 记录更新不正确 解决方案:
  • 如果需要触发规则,请移除 setWorkflow(false)
  • 验证字段更新是否有效
  • 检查业务规则条件

相关技能

  • admin/script-execution - 后台脚本执行
  • admin/update-set-management - 跟踪批量操作更改
  • admin/user-provisioning - 批量用户操作
  • admin/deployment-workflow - 在实例之间部署批处理脚本

参考资料

评论

支持 Markdown