
1. 为什么你的 ASP 页面一取 RecordCount 就是 -1如果你正在维护一套经典的 ASP 系统页面里写着rs.RecordCount结果分页条永远显示「共 -1 条」或者If rs.RecordCount 0 Then死活进不去那你遇到的就是 ADO 里最经典的一个坑。RecordCount 返回 -1 不是数据库坏了也不是连接字符串写错了绝大多数情况下是游标类型CursorType选得不对。ADO 的 Recordset 在默认的adOpenForwardOnly模式下本质是一个只能向前滚动的流提供者根本不知道后面还有多少条记录所以它诚实地告诉你「我不知道」——用 -1 表示。这个行为是设计决定的不是 bug。这篇文章面向的是还在用 ASP ADO 访问 Access、SQL Server 的开发者尤其是那些接手老项目、需要快速定位分页和计数问题的人。我会把 CursorType、LockType 对 RecordCount 的影响讲清楚给出可以直接复制的连接字符串和游标配置骨架再补一段用 TaoToken 统一管理 Key 和 API 通道的 settings.json 配置示例方便你在排查完数据库侧问题后顺手把调用外部模型接口的配置也理顺。整篇按「现象 → 原因 → 可复制配置 → 验证 → 排错 → 收尾」的顺序走你可以边看边改。先说结论想让RecordCount返回真实行数服务器端游标要用adOpenKeyset1或adOpenStatic3客户端游标则无论你传什么ADO 都会用adOpenStatic。但这里有个前提——你的数据提供者得支持你选的游标类型否则它可能悄悄降级成 ForwardOnlyRecordCount 又变回 -1。2. CursorType 与 LockType 到底怎么影响 RecordCount2.1 四种 CursorType 的行为差异ADO 定义了四种游标类型它们对「能否知道总记录数」这件事的态度完全不同。下面这张表是我在实际项目里反复验证后整理的建议收藏。常量值能否自由移动是否反映他人修改RecordCount 是否可用adOpenForwardOnly0只能向前否否返回 -1adOpenKeyset1可自由移动反映修改/删除不反映新增是adOpenDynamic2可自由移动全部反映视提供者而定常为 -1adOpenStatic3可自由移动不反映是关键点在于adOpenForwardOnly是缺省值也是性能最好的但它天生不支持 RecordCount。而adOpenDynamic虽然功能最强却因为记录数随时可能变化很多提供者干脆也不返回准确计数。所以真正稳妥的只有 Keyset 和 Static 两个。2.2 LockType 会反过来强制改变 CursorType很多人不知道LockType 和 CursorType 不是独立的。当你设置了一个与当前游标不兼容的 LockType 时ADO 或提供者会强制切换 CursorType。比如你把 LockType 设成adLockBatchOptimistic4某些提供者会要求游标必须是 Static 或 Keyset于是你原本设的 ForwardOnly 就被顶掉了。反过来如果你只想要 RecordCount却设了adLockReadOnly1配 ForwardOnly那 -1 就是必然结果。注意不是所有 LockType 和 CursorType 的组合都能同时工作。改 LockType 可能悄悄改掉 CursorType所以调试时一定要把rs.CursorType和rs.LockType的实际值打印出来看。2.3 提供者差异Access 和 SQL Server 表现不同用 OLEDB Provider for JETAccess和 SQL Server 时同样的代码可能得到不同结果。Access 的提供者对 Dynamic 游标支持有限经常降级SQL Server 则相对规范但对服务器端游标和客户端游标区分更严格。所以别照搬网上的代码先确认你连的是什么库。3. TaoToken 前置把 Key 和 API 通道统一管起来排查完数据库侧很多 ASP 项目现在还会调用外部模型接口做内容审核、摘要或客服回复。这时候如果每个页面都硬编码 Key维护起来很痛苦。我的做法是用 TaoToken 做统一入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它把模型对话、Coding Plan、控制台、API Keys 这些入口都收在一处你只需要在配置里维护一个 Key 和一个 base URL。具体来说你可以在 TaoToken 控制台生成 Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在项目里用一个 settings.json 统一读取。这样 ASP 页面里调用模型接口时就不用到处改连接信息了。如果你主要做长期编码或 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 只是临时验证模型效果用模型对话https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 就够了。4. 可复制的连接字符串与游标配置骨架4.1 服务器端游标的标准写法下面这段是 Access 场景下我实测能拿到正确 RecordCount 的骨架。注意CursorType和LockType分开设置别用rs.Open sql, conn, 1, 1这种简写因为简写里两个 1 的含义容易记混。% Dim conn, rs, sql Set conn Server.CreateObject(ADODB.Connection) conn.Open ProviderMicrosoft.Jet.OLEDB.4.0;Data Source Server.MapPath(/data/site.mdb) Set rs Server.CreateObject(ADODB.Recordset) rs.CursorType 3 adOpenStatic rs.LockType 1 adLockReadOnly rs.CursorLocation 2 adUseServer服务器端游标 sql SELECT id, title FROM articles ORDER BY id DESC rs.Open sql, conn Response.Write 总记录数 rs.RecordCount %如果你需要反映其他用户的修改把CursorType改成 1adOpenKeyset即可。但要注意 Keyset 不反映新增记录分页时可能出现「总数不变但翻页看到新数据」的错觉。4.2 客户端游标的写法客户端游标会把整个结果集拉到内存RecordCount 一定准确但数据量大时很吃内存。适合结果集小、需要频繁随机访问的场景。% Set rs Server.CreateObject(ADODB.Recordset) rs.CursorLocation 3 adUseClient rs.CursorType 3 adOpenStatic客户端下其实传什么都一样 rs.LockType 1 rs.Open SELECT * FROM orders WHERE status1, conn Response.Write rs.RecordCount %4.3 settings.json 配置示例把 TaoToken 的 Key 和 base URL 放进 settings.jsonASP 侧用 FileSystemObject 读取避免硬编码。{ taotoken: { apiKey: sk-你的Key, baseUrl: https://taotoken.net/api, model: claude-sonnet, timeout: 30 }, database: { provider: Microsoft.Jet.OLEDB.4.0, source: /data/site.mdb } }读取时用Scripting.FileSystemObject打开文件再用JSON解析ASP 原生没有 JSON 解析可以引入一个轻量解析函数或者把配置写成keyvalue的 ini 格式更省事。如果你不想引入额外依赖直接把 Key 放在Application对象里启动时读一次也行。5. 验证请求与成功结果改完游标后别急着上线先写一个最小验证页。下面这段会同时打印 CursorType、LockType 和 RecordCount一眼就能看出提供者有没有偷偷降级。% Dim conn, rs Set conn Server.CreateObject(ADODB.Connection) conn.Open ProviderMicrosoft.Jet.OLEDB.4.0;Data Source Server.MapPath(/data/site.mdb) Set rs Server.CreateObject(ADODB.Recordset) rs.CursorType 3 rs.LockType 1 rs.CursorLocation 2 rs.Open SELECT id FROM articles, conn Response.Write 实际 CursorType rs.CursorType br Response.Write 实际 LockType rs.LockType br Response.Write RecordCount rs.RecordCount br Response.Write PageCount rs.PageCount br rs.Close conn.Close %预期输出是实际 CursorType3、RecordCount等于真实行数、PageCount大于 0。如果 CursorType 显示 0说明提供者不支持你选的类型降级了这时候要么换提供者要么改用客户端游标。验证模型接口是否通可以在 TaoToken 模型对话页https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条测试消息确认 Key 和 base URL 没问题再回到 ASP 里接。6. 本篇常见错排查6.1 RecordCount 还是 -1先看rs.CursorType实际值。如果被降级成 0检查提供者是否支持 Static。Access 用 JET 时服务器端 Static 有时也不支持改用CursorLocation 3走客户端。SQL Server 则建议用adUseServer配 Keyset。6.2 分页时 PageCount 为 -1PageCount 依赖 RecordCount 和 PageSize。RecordCount 不对PageCount 自然也不对。先修 RecordCount再设rs.PageSize 20然后rs.AbsolutePage 2测试翻页。6.3 改了 LockType 后游标又变了这是前面说的强制切换。把 LockType 固定成adLockReadOnly1只读场景下它和 Static、Keyset 都兼容最不容易出意外。6.4 连接字符串里的 Provider 写错Access 2007 以上是Microsoft.ACE.OLEDB.12.0老版本是Microsoft.Jet.OLEDB.4.0。写错会直接报「未找到提供者」跟 RecordCount 无关但排查时容易混淆。6.5 客户端游标内存暴涨结果集超过几万行时adUseClient会把数据全拉进内存。这时候改用服务器端 Keyset或者干脆用SELECT COUNT(*)单独查总数再分页查数据性能更好。7. 收尾把配置和 Key 都收进一处RecordCount 的问题本质是游标选择问题记住「要计数就用 Keyset 或 StaticForwardOnly 永远返回 -1」这一条大部分场景就能解决。剩下的就是根据提供者能力做取舍数据量小用客户端 Static 最省心数据量大用服务器端 Keyset 配只读锁。至于外部接口的 Key 管理建议你尽早用 TaoToken 统一起来。API Keys 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 生成接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 可以查到具体参数。如果你用 Claude Code 做开发Anthropic 兼容入口在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。把这些地址和 Key 一起写进 settings.json下次换项目就不用满世界找配置了。