1. 遗留系统里批量更新为什么总翻车
如果你维护过 VB6、ASP 或者 .NET 早期版本的老系统,大概率见过这种写法:循环里一条条Update,几千行数据跑下来,数据库连接占着不放,中间一旦报错,前面改了一半后面没改,数据直接对不上。更麻烦的是,有些字段更新依赖前一条的结果,逐条提交时根本没法回滚到一致状态。
Recordset 对象本身是支持批处理的。核心就三个东西:CursorType(游标类型)、LockType(锁定类型)、Open方法的参数配合,最后用UpdateBatch一次性把缓存里的改动推给数据库。但很多人卡在第一步——游标选错了,UpdateBatch直接抛异常或者静默失效。
这篇聚焦的就是这套配置骨架:怎么选游标、怎么设锁、Open怎么写、UpdateBatch提交后怎么逐条验证结果。适合还在维护经典 ADO 代码、需要在不重写架构的前提下把批处理跑稳的人。下面给的连接字符串和参数片段可以直接复制改,验证动作也按步骤写清楚。
2. 前置准备:连接与 TaoToken 接入配置
批处理要跑通,连接层得先稳。经典 ADO 用ADODB.Connection打开数据源,连接字符串的写法直接决定后面 Recordset 能不能拿到服务端游标。如果你是通过统一网关接入数据库或模型服务,把网关地址和 Key 配好再往下走,能省掉很多网络层的排查时间。
TaoToken 的接入入口在这里:API 地址是https://taotoken.net/api,控制台里可以创建和管理 Key。先拿到 API Key,后面连接配置里会用到。
' 经典 ADO 连接骨架(以 SQL Server 为例) Dim conn As ADODB.Connection Set conn = New ADODB.Connection ' 直连数据库的 Provider 写法 conn.ConnectionString = "Provider=SQLOLEDB;Data Source=127.0.0.1;" & _ "Initial Catalog=LegacyDB;User ID=sa;Password=your_pwd;" conn.CursorLocation = adUseServer ' 批处理必须用服务端游标 conn.Open这里有个关键点:CursorLocation要设成adUseServer。客户端游标(adUseClient)虽然灵活,但批更新能力受限,很多提供者根本不支持UpdateBatch。服务端游标把游标管理交给数据库,批处理才有落脚点。
如果你走的是统一 API 网关,Key 的创建入口在控制台的 API Keys 页面,接入文档里有完整的鉴权头格式。把 Key 配到环境变量或配置表里,别硬编码在源码中。
3. 可复制配置:CursorType、LockType 与 Open 参数
这是整篇的核心骨架。Recordset 打开时,CursorType和LockType的组合决定了你能不能批更新、能看到哪些并发改动。
Dim rs As ADODB.Recordset Set rs = New ADODB.Recordset ' 关键三参数:源、连接、游标类型、锁定类型 rs.Open "SELECT id, name, amount, status FROM Orders WHERE status = 'PENDING'", _ conn, _ adOpenKeyset, _ ' 游标类型:键集游标 adLockBatchOptimistic, _ ' 锁定类型:批乐观锁 adCmdText四个游标类型的取舍,直接看这张对照表:
| CursorType | 常量值 | 能否 UpdateBatch | 适用场景 |
|---|---|---|---|
| 仅向前 | adOpenForwardOnly | 否 | 只读遍历、报表导出 |
| 静态 | adOpenStatic | 客户端下受限 | 快照查询、离线分析 |
| 键集 | adOpenKeyset | 是 | 批更新首选,能看到他人改值 |
| 动态 | adOpenDynamic | 是(提供者支持时) | 高并发实时视图 |
LockType这边,批处理必须用adLockBatchOptimistic。它的含义是:改动先存本地缓存,不立刻锁库,等UpdateBatch时再统一提交并做冲突检测。如果你用了adLockOptimistic(立即乐观锁),每次Update都会直接写库,UpdateBatch就失去意义了。
Open方法的完整签名是Open(Source, ActiveConnection, CursorType, LockType, Options)。Source可以是表名、SQL 语句或 Command 对象。批处理场景建议用 SQL 语句精确圈定要改的行,别整表打开。
打开后先确认游标真的生效了:
Debug.Print rs.CursorType ' 应为 3 (adOpenKeyset) Debug.Print rs.LockType ' 应为 4 (adLockBatchOptimistic) Debug.Print rs.CursorLocation ' 应为 2 (adUseServer)如果CursorType打印出来是 0 或 1,说明提供者降级了游标,UpdateBatch大概率会失败。这时候要么换 Provider,要么检查连接字符串里有没有强制客户端游标的参数。
4. 批量修改与 UpdateBatch 提交
配置对了,改数据就是标准动作。用AddNew、Update、Delete往缓存里塞改动,最后一次性提交。
rs.MoveFirst Do While Not rs.EOF If rs!amount < 100 Then rs!status = "REVIEW" ' 改字段,不调 Update rs!amount = rs!amount * 1.1 End If rs.MoveNext Loop ' 一次性提交所有缓存改动 rs.UpdateBatch注意循环里不要调rs.Update。在批乐观锁模式下,直接给字段赋值就会把改动标记到缓存,Update反而会触发单条提交。这是最常见的踩坑点。
提交后,用Status属性逐条检查结果。UpdateBatch支持传参指定冲突处理方式,比如adAffectAll或adAffectCurrent:
rs.UpdateBatch adAffectAll ' 逐条校验 rs.MoveFirst Do While Not rs.EOF Select Case rs.Status Case adRecUnmodified ' 未改动,正常 Case adRecModified ' 已成功修改 Case adRecDBDeleted ' 记录已被他人删除 Case adRecConcurrencyViolation ' 并发冲突,需要人工介入 Debug.Print "冲突行 id=" & rs!id End Select rs.MoveNext LoopStatus是位掩码,可能同时包含多个标志。判断时用And运算更稳妥,比如If (rs.Status And adRecConcurrencyViolation) <> 0 Then。
5. 常见报错与排查清单
批处理跑不起来,八成是下面几个原因。按顺序排查:
报错「当前提供者不支持 UpdateBatch」:CursorLocation被设成了adUseClient,或者 Provider 本身不支持批更新。改回adUseServer,换SQLOLEDB或MSDASQL试试。
UpdateBatch返回成功但数据没变:循环里误调了rs.Update,改动被逐条提交后又重置了缓存。去掉循环里的Update调用。
并发冲突大量出现:LockType用了adLockBatchOptimistic但没处理Status。要么在提交前用Filter缩小范围,要么在冲突时用Resync拉取最新值再决定覆盖策略。
BOF/EOF异常:查询结果为空时直接MoveFirst会报错。打开后先判断If rs.EOF And rs.BOF Then,空集直接跳过批处理。
连接被占用导致超时:多个 Recordset 共用同一个 Connection 时,显式创建 Connection 对象并复用,别在Open里传连接字符串让 ADO 隐式新建。
' 显式复用连接,避免隐式新建 Set rs.ActiveConnection = conn rs.Open "SELECT ...", , adOpenKeyset, adLockBatchOptimistic, adCmdText排查时打开 SQL Profiler 看实际发出的 SQL,能最快定位是游标降级还是提交没触发。
6. 接入与验证入口
批处理逻辑跑通后,如果你需要把数据同步到模型服务或做进一步处理,Key 的管理和接入文档在控制台里。API Keys 页面创建 Key,接入文档里有完整的请求格式和错误码说明。
验证模型连通性可以用模型对话页面直接测一条请求,确认网关层没问题再回到 ADO 批处理链路。长期跑编码任务或 Agent 场景的话,Coding Plan 的配额和调用方式在对应页面有说明。
整套骨架的核心就一句话:服务端游标 + 键集 + 批乐观锁 + 循环内不调 Update + 提交后查 Status。把这五步固定成模板,遗留系统里的批量更新就能稳定落地。