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返回真实行数,服务器端游标要用adOpenKeyset(=1)或adOpenStatic(=3);客户端游标则无论你传什么,ADO 都会用adOpenStatic。但这里有个前提——你的数据提供者得支持你选的游标类型,否则它可能悄悄降级成 ForwardOnly,RecordCount 又变回 -1。
2. CursorType 与 LockType 到底怎么影响 RecordCount
2.1 四种 CursorType 的行为差异
ADO 定义了四种游标类型,它们对「能否知道总记录数」这件事的态度完全不同。下面这张表是我在实际项目里反复验证后整理的,建议收藏。
| 常量 | 值 | 能否自由移动 | 是否反映他人修改 | RecordCount 是否可用 |
|---|---|---|---|---|
| adOpenForwardOnly | 0 | 只能向前 | 否 | 否,返回 -1 |
| adOpenKeyset | 1 | 可自由移动 | 反映修改/删除,不反映新增 | 是 |
| adOpenDynamic | 2 | 可自由移动 | 全部反映 | 视提供者而定,常为 -1 |
| adOpenStatic | 3 | 可自由移动 | 不反映 | 是 |
关键点在于:adOpenForwardOnly是缺省值,也是性能最好的,但它天生不支持 RecordCount。而adOpenDynamic虽然功能最强,却因为记录数随时可能变化,很多提供者干脆也不返回准确计数。所以真正稳妥的只有 Keyset 和 Static 两个。
2.2 LockType 会反过来强制改变 CursorType
很多人不知道,LockType 和 CursorType 不是独立的。当你设置了一个与当前游标不兼容的 LockType 时,ADO 或提供者会强制切换 CursorType。比如你把 LockType 设成adLockBatchOptimistic(=4),某些提供者会要求游标必须是 Static 或 Keyset,于是你原本设的 ForwardOnly 就被顶掉了。反过来,如果你只想要 RecordCount,却设了adLockReadOnly(=1)配 ForwardOnly,那 -1 就是必然结果。
注意:不是所有 LockType 和 CursorType 的组合都能同时工作。改 LockType 可能悄悄改掉 CursorType,所以调试时一定要把
rs.CursorType和rs.LockType的实际值打印出来看。
2.3 提供者差异:Access 和 SQL Server 表现不同
用 OLEDB Provider for JET(Access)和 SQL Server 时,同样的代码可能得到不同结果。Access 的提供者对 Dynamic 游标支持有限,经常降级;SQL Server 则相对规范,但对服务器端游标和客户端游标区分更严格。所以别照搬网上的代码,先确认你连的是什么库。
3. TaoToken 前置:把 Key 和 API 通道统一管起来
排查完数据库侧,很多 ASP 项目现在还会调用外部模型接口做内容审核、摘要或客服回复。这时候如果每个页面都硬编码 Key,维护起来很痛苦。我的做法是用 TaoToken 做统一入口,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。它把模型对话、Coding Plan、控制台、API Keys 这些入口都收在一处,你只需要在配置里维护一个 Key 和一个 base URL。
具体来说,你可以在 TaoToken 控制台生成 Key(https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ),然后在项目里用一个 settings.json 统一读取。这样 ASP 页面里调用模型接口时,就不用到处改连接信息了。如果你主要做长期编码或 Agent 类任务,可以看 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite );只是临时验证模型效果,用模型对话(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite )就够了。
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 "Provider=Microsoft.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改成 1(adOpenKeyset)即可。但要注意 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 status=1", conn Response.Write rs.RecordCount %>4.3 settings.json 配置示例
把 TaoToken 的 Key 和 base URL 放进 settings.json,ASP 侧用 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 解析,可以引入一个轻量解析函数,或者把配置写成key=value的 ini 格式更省事)。如果你不想引入额外依赖,直接把 Key 放在Application对象里,启动时读一次也行。
5. 验证请求与成功结果
改完游标后,别急着上线,先写一个最小验证页。下面这段会同时打印 CursorType、LockType 和 RecordCount,一眼就能看出提供者有没有偷偷降级。
<% Dim conn, rs Set conn = Server.CreateObject("ADODB.Connection") conn.Open "Provider=Microsoft.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 %>预期输出是实际 CursorType=3、RecordCount等于真实行数、PageCount大于 0。如果 CursorType 显示 0,说明提供者不支持你选的类型,降级了,这时候要么换提供者,要么改用客户端游标。
验证模型接口是否通,可以在 TaoToken 模型对话页(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite )发一条测试消息,确认 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 为 -1
PageCount 依赖 RecordCount 和 PageSize。RecordCount 不对,PageCount 自然也不对。先修 RecordCount,再设rs.PageSize = 20,然后rs.AbsolutePage = 2测试翻页。
6.3 改了 LockType 后游标又变了
这是前面说的强制切换。把 LockType 固定成adLockReadOnly(=1),只读场景下它和 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 或 Static,ForwardOnly 永远返回 -1」这一条,大部分场景就能解决。剩下的就是根据提供者能力做取舍:数据量小用客户端 Static 最省心,数据量大用服务器端 Keyset 配只读锁。
至于外部接口的 Key 管理,建议你尽早用 TaoToken 统一起来。API Keys 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 生成,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 可以查到具体参数。如果你用 Claude Code 做开发,Anthropic 兼容入口在 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite 。把这些地址和 Key 一起写进 settings.json,下次换项目就不用满世界找配置了。