CAdoDatabase实战:C++连接Oracle的ADO封装与避坑指南

发布时间:2026/10/29 7:22:42
CAdoDatabase实战:C++连接Oracle的ADO封装与避坑指南 简介CAdoDatabase.rar是一份面向C初学者的ADO数据库编程练习包聚焦通过ActiveX Data Objects访问Oracle数据库的完整流程可直接作为课程设计或日常练手的参考样例。包内含40个文件以12个头文件和8个C源文件为核心配合工程配置、资源脚本及文本说明其中.h/.cpp存放类与实现、.dsp/.dsw为工程文件、txt提供使用说明整体仅209KB代码量紧凑适合逐行阅读。内容覆盖ADO核心对象的使用包括Connection建立连接、Command执行SQL、Recordset遍历结果、Parameter参数化查询并涉及连接字符串配置、事务处理、错误检测与连接池等关键机制通过阅读和运行其中的示例还能直观了解C工程中引用ADO类型库、封装数据库操作类、处理Oracle交互细节的常见写法。目前已有130人浏览学习对于想理清ADO编程脉络或准备数据库访问代码的开发者而言是一份轻量且可参考的入门素材。1. 先用五分钟搞懂CAdoDatabase 到底解决了什么在 Windows 上用 C 写 Oracle 客户端的老项目里今天还在被反复搬运的代码清单除了 OCCI 和 Pro*C一定还有一套 ADO 封装类。它通常长成 CAdoDatabase、CAdoRecordset、CAdoCommand 几个类把 ADO 的 _ConnectionPtr、_RecordsetPtr、_CommandPtr 再裹一层上层代码只需要 Open、ExecuteQuery、GetFieldValue 这几个白话方法。CAdoDatabase 这个名字指的就是其中最核心的连接管理类它解决的问题很具体让不熟悉 COM 的同事也能在三天内写好一个能连 Oracle、能跑查询、能拿结果的小工具同时把 BSTR、_variant_t、类型库导入这些细节全部挡在外面。这篇文章不考古某个压缩包的内部实现而是按这一类封装最常见的写法从搭建环境、连接串选型、核心方法到中文乱码和排障一条龙讲清楚。适合刚接手维护这种老代码的工程师也适合想在 Visual C 项目里自建一个数据访问层、却不想每次裸调 ADO COM 接口的人。看完你至少能自己把 CAdoDatabase 复写出来并且知道在 Oracle 11g、12c 上哪些参数会让你翻车。2. 搭建 Oracle 连接的 C 环境COM 初始化、ADO 类型库与连接串选型2.1 把 ADO 类型库导进来还有比 #import 更省事的办法吗大多数 CAdoDatabase 源码的第一步都是#import msado15.dll。这一句让 MSVC 读取 COM 类型库并生成 _ConnectionPtr、_RecordsetPtr、_CommandPtr 三个智能指针包装类后面的代码才能直接 new 对象、调方法不用手动管 AddRef/Release。#import C:\Program Files\Common Files\System\ADO\msado15.dll \ no_namespace \ rename(EOF, adoEOF) \ rename(BOF, adoBOF)这段头文件要放在公共头文件里并且放在所有使用 ADO 类型的代码之前。no_namespace让类型直接暴露避免ADODB::_ConnectionPtr_t这种长写法但代价是可能与其他 COM 库命名冲突。rename是必须的ADO 库里的 EOF/BOF 宏会跟标准 C 头文件的 EOF 撞车不重命名编译期就能制造大量莫名其妙的错误。少数项目还会rename(Connection, ADOConnection)或rename(Recordset, ADORecordset)这是为了避开 MFC 同名类如果你不依赖 MFC 也不用刻意学。另一种写法是#import msado15.dll让编译器通过 INCLUDE 路径去搜。VS2022 一般能找到老 VC6 则建议路径写死。两个写法差别不大你只需要保证整个工程只 import 一次别在两个 cpp 里重复导入相同类型库否则会出现大量 “warning C4002” 或重复定义错误。2.2 初始化 COM 与 CAdoDatabase 的最小可编译骨架MFC 工程如果已经用 AfxOleInit 在启动阶段初始化过 COMCAdoDatabase 的 Open 里再调 CoInitialize 并不会报错只会返回 S_FALSE。但很多封装类不注意配平问题一个线程已经初始化过 COM你 Close 时又去调 CoUninitialize后续其他 COM 调用就可能出现 “未调用 CoInitialize” 的崩溃。我习惯在 Open 里记录本次调用是否真的完成了首次初始化。class CAdoDatabase { public: CAdoDatabase() : m_pConnection(NULL), m_bIsOpen(FALSE), m_bNeedCoUninit(FALSE) {} virtual ~CAdoDatabase() { Close(); } BOOL Open(LPCTSTR lpConnStr, int nConnectTimeout 15); void Close(); _ConnectionPtr GetConnection() { return m_pConnection; } BOOL IsOpen() const { return m_bIsOpen; } CString GetLastError() const { return m_strLastError; } protected: _ConnectionPtr m_pConnection; BOOL m_bIsOpen; BOOL m_bNeedCoUninit; CString m_strLastError; }; BOOL CAdoDatabase::Open(LPCTSTR lpConnStr, int nConnectTimeout) { Close(); HRESULT hr ::CoInitialize(NULL); m_bNeedCoUninit (hr S_OK); // S_FALSE 说明线程已初始化过不要配平 hr m_pConnection.CreateInstance(__uuidof(Connection)); if (FAILED(hr)) { m_strLastError _T(CreateInstance(Connection) failed); return FALSE; } m_pConnection-ConnectionTimeout nConnectTimeout; try { hr m_pConnection-Open(_bstr_t(lpConnStr), _bstr_t(_T()), _bstr_t(_T()), adConnectUnspecified); } catch (_com_error e) { m_strLastError _T(Open exception: ) CString(e.ErrorMessage()); return FALSE; } m_bIsOpen SUCCEEDED(hr); return m_bIsOpen; } void CAdoDatabase::Close() { if (m_pConnection ! NULL m_bIsOpen) { m_pConnection-Close(); } m_pConnection NULL; m_bIsOpen FALSE; if (m_bNeedCoUninit) { ::CoUninitialize(); m_bNeedCoUninit FALSE; } }CreateInstance 内部就是 COM 的 CoCreateInstance生成 ADODB.Connection 对象。Open 的第二、三个参数分别是 UserID 和 Password传空串表示从连接串读取。adConnectUnspecified让 OLE DB 自己决定连接属性不需要手工指定。捕获_com_error而不是裸跑的原因是 ADO 智能指针在失败时主动抛异常不 catch 程序直接崩。注意两点。第一这里把m_bNeedCoUninit置为(hr S_OK)如果返回 S_FALSE说明线程已经初始化过 COM本次 Close 绝不 CoUninitialize。第二纯 Win32 程序用 CoInitialize 没问题MFC 对话框程序则推荐 AfxOleInit因为内部会设置 OLE 全局状态。如果你的类想两头通用就按我上面的写法再加一个 BOOL 成员让用户决定是否跳过自动初始化。2.3 Oracle 连接串在 C 里怎么安全拼装Provider、TNS_ADMIN 与直连 DESCRIPTOR很多 CAdoDatabase 的 Open 接口只认一条连接字符串拼字符串的手法反而成了最容易出 bug 的地方。看三种最常用的 Oracle OLE DB 连接串// 形式1走 tnsnames.ora 里的网络服务名 CString strConn; strConn.Format(_T(ProviderOraOLEDB.Oracle.1;Data Source%s; User Id%s;Password%s;), strTnsName, strUser, strPass); // 形式2直接把地址写进连接串不强依赖 tnsnames.ora CString strDesc; strDesc.Format(_T((DESCRIPTION(ADDRESS(PROTOCOLTCP) (HOST%s)(PORT%d)) (CONNECT_DATA(SERVICE_NAME%s)))), strHost, nPort, strService); strConn.Format(_T(ProviderOraOLEDB.Oracle;Data Source%s; User Id%s;Password%s;), strDesc, strUser, strPass); // 形式3老项目的 MSDAORA只适合临时验证 strConn.Format(_T(ProviderMSDAORA.1;Data Source%s; User Id%s;Password%s;), strTnsName, strUser, strPass);这段代码里有两个 C 特有的大坑。第一CString::Format 遇到%是转义符密码里只要含%就要写成%%否则后面的内容全丢。第二OLE DB 连接串用分号分隔键值对密码里带分号会把后续内容吞掉这种情况建议改用_CommandPtr的 Properties 设置 UID/PWD而不是拼串。Provider 的选型可以直接看表Provider 字符串需要 Oracle Client 吗中文与大数据类型支持适用场景ProviderOraOLEDB.Oracle.1需要含 Instant Client好按 NLS_LANG 转换生产环境首选ProviderMSDAORA.1不需要系统自带差CLOB/NUMBER 精度会打折临时验证、老库ProviderMSDASQL Oracle ODBC Driver需要 ODBC Driver一般公司强制 ODBC 时用问题往往不是 Provider 本身而是客户端环境。Oracle 客户端装好后系统要有TNS_ADMIN指向 tnsnames.ora 所在目录否则Data SourceORCL解析不了。写代码之前先命令行跑一遍tnsping ORCLUsed TNSNAMES adapter to resolve the alias Attempting to contact (DESCRIPTION... OK (0 msec)如果 tnsping 失败问题在客户端不在 ADO。不要无脑调代码先解决网络名解析和监听。3. CAdoDatabase 的核心操作执行 SQL、遍历 Recordset 与参数绑定3.1 查询方法为什么封装成 Recordset.Open而不是 Connection.Execute在 CAdoDatabase 里跑 SELECT最常见做法是用 Recordset 的 Open而不是 Connection 的 Execute。Connection.Execute 返回的是一个流动型只读结果集适合一把梭读完Recordset.Open 却能控制游标、锁、命令类型还能复用参数。封装一个最小查询方法_RecordsetPtr CAdoDatabase::ExecuteQuery(LPCTSTR lpSQL) { if (!IsOpen()) return NULL; _RecordsetPtr pRs; HRESULT hr pRs.CreateInstance(__uuidof(Recordset)); if (FAILED(hr)) return NULL; pRs-CursorLocation adUseClient; try { pRs-Open(_variant_t(lpSQL), m_pConnection.GetInterfacePtr(), adOpenForwardOnly, adLockReadOnly, adCmdText); } catch (_com_error e) { m_strLastError _T(ExecuteQuery: ) CString(e.ErrorMessage()); return NULL; } return pRs; }adUseClient是最容易被讨论的参数它把结果集拉到客户端内存游标可以自由滚动连接占用时间短但数据量大时内存暴涨我不建议默认使用。上面这段是为了演示而写实际封装我会留一个BOOL bClientCursor参数默认 FALSE。adOpenForwardOnly表示只能 MoveNext性能最好adLockReadOnly只读防止误改数据。真要更新数据库用adOpenKeyset adLockOptimistic不过大部分项目走存储过程不直接在 ADO 里做 Update。3.2 遍历结果集并读字段从 _variant_t 到 CString 的安全切换记录集的值通过字段对象的 Value 属性返回一个 VARIANTOracle 的空值是 VT_NULL。很多老代码直接(LPCTSTR)(_bstr_t)vtVal遇到 NULL 就崩。封装时我会加一个专门的取值函数BOOL CAdoDatabase::GetFieldValue(_RecordsetPtr pRs, LPCTSTR lpFieldName, CString strOut) { if (pRs NULL || pRs-adoEOF) return FALSE; try { _variant_t vtVal pRs-Fields-GetItem(lpFieldName)-Value; if (vtVal.vt VT_NULL || vtVal.vt VT_EMPTY) { strOut.Empty(); return TRUE; } _bstr_t bstrVal (_bstr_t)vtVal; strOut (LPCTSTR)bstrVal; return TRUE; } catch (_com_error) { return FALSE; } }逻辑说明字段不存在、字段为空、类型不匹配都可能抛_com_error所以全程 catch。(_bstr_t)vtVal是 ADO 最常用的转换但只能对 VT_BSTR、VT_I4、VT_DATE 这类基础类型管用遇到 CLOB 字段Value 可能是一个流接口直接转会抛。CLOB 需要分块读后面参数绑定部分再说。遍历一行就很简单while (!pRs-adoEOF) { CString strName, strAge; GetFieldValue(pRs, _T(NAME), strName); GetFieldValue(pRs, _T(AGE), strAge); // 处理这一行 pRs-MoveNext(); } pRs-Close();注意pRs-adoEOF是重命名后的 EOF。忘记 rename 的话编译时 EOF 被标准 C 头文件抢走会产生让你排查半天的编译错误。3.3 带参数查询与存储过程调用Parameter 绑定前先想清楚类型接下来是存储过程封装。很多 CAdoDatabase 用_CommandPtr来支持 IN 参数最稳的写法是循环 Append 参数_RecordsetPtr CAdoDatabase::ExecuteProc(LPCTSTR lpProcName, const CStringArray arrInput) { if (!IsOpen()) return NULL; _CommandPtr pCmd; pCmd.CreateInstance(__uuidof(Command)); pCmd-ActiveConnection m_pConnection.GetInterfacePtr(); pCmd-CommandText _bstr_t(lpProcName); pCmd-CommandType adCmdStoredProc; for (int i 0; i arrInput.GetSize(); i) { CString strParamName; strParamName.Format(_T(P%d), i); pCmd-Parameters-Append( pCmd-CreateParameter( _bstr_t(strParamName), adVarChar, adParamInput, arrInput[i].GetLength() 1, _variant_t(arrInput[i]))); } return pCmd-Execute(NULL, NULL, adCmdStoredProc); }CreateParameter 的五个参数分别是参数名、类型、方向、长度、值。Oracle 用绑定变量的方式要求类型非常严adVarChar对应 VARCHAR2adInteger对应 NUMBER(9)。遇到 ORA-01460 这类错误基本可以断定是长度或类型给得不对尤其不要把 LONG 字段当 VARCHAR2 处理。对于 CLOB要用adLongVarChar并且长度要足够容纳整块数据。另一个容易踩的坑是 OUT 参数。如果存储过程有返回参数必须在 Execute 前预先 Append 一个adParamOutput参数执行后再从pCmd-Parameters-GetItem(n)-Value读取。有人图省事用pCmd-Parameters-Refresh()让 Provider 自动读参数表这在某些 OraOLEDB 版本上会失败或读不到建议别依赖这个黑匣子。3.4 分页查询在 SQL 里分页别把整表装进 ADO 再翻页涉及大数据量时一定要在 SQL 层做分页。Oracle 12c 前最常用 ROW_NUMBER 嵌套SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (ORDER BY id) AS RN FROM big_table t ) WHERE RN BETWEEN :start AND :end在 OraOLEDB 下把:start和:end绑成 int 参数。Oracle 12c 以后也能用OFFSET :m ROWS FETCH NEXT :n ROWS ONLY但 ADO 参数绑定对这种写法的兼容性不如 ROW_NUMBER实测更稳的是上面这版。为什么特意强调因为新手常犯的错是SELECT * FROM big_table全量拉回来再用 ADO 游标跳到第 N 条。数据量过了五万行客户端内存和连接占用都会告急这个习惯得从一开始就改掉。4. 数据转换里最容易翻车的三角关系BSTR、_variant_t 与 Oracle 字符集4.1 CString 转 BSTR_bstr_t 背后有隐含的字符集假设从标题可以看出ADO 所有字符串参数最终都是 BSTR 宽字符。C 项目如果开了 Unicode 字符集VS2022 默认CString 底层是宽字符直接赋给_bstr_t是安全的。但很多存量项目还是“多字节字符集 (MBCS)”这时候会先按系统 ACP 转成 UnicodeOracle 端拿到的字节序列和代码里以为的内容就可能对不上。// 推荐MBCS 工程里显式用 UTF-8 或者本机 ACP 转成宽字符避免依赖编译器默认行为 #ifdef _UNICODE _bstr_t bstrConn(lpConnStr); #else CStringA strA(lpConnStr); int nWLen MultiByteToWideChar(CP_ACP, 0, strA, -1, NULL, 0); CStringW strW; MultiByteToWideChar(CP_ACP, 0, strA, -1, strW.GetBuffer(nWLen), nWLen); strW.ReleaseBuffer(); _bstr_t bstrConn(strW); #endif很多 CAdoDatabase 的源码不会管这个细节直接把 LPCTSTR 丢给_bstr_t。在简体中文系统上这个坑看不出一旦连接串里的中文密码出现在日文或英文版机器上就会验证失败而且日志里没有任何乱码提示。就是玄学但它确实是真实存在的。4.2 提取数字和日期别用强转交给 VariantChangeType上一节提过(_bstr_t)vtVal只能处理基础类型。数字字段更稳的取值方式是用 VariantChangeTypeint CAdoDatabase::GetFieldInt(_RecordsetPtr pRs, LPCTSTR lpFieldName, int nDefault) { _variant_t vtVal; vtVal pRs-Fields-GetItem(lpFieldName)-Value; if (vtVal.vt VT_NULL || vtVal.vt VT_EMPTY) return nDefault; _variant_t vtNumber; HRESULT hr VariantChangeType(vtNumber, vtVal, 0, VT_I4); if (SUCCEEDED(hr)) return vtNumber.lVal; return nDefault; }要用 EVariant不要直接(int)vtVal。C 风格强转遇到超范围值、浮点字符串、日期类型时结果是未定义的会让你在线上抓狂。4.3 中文乱码NLS_LANG 和 Provider 的双重夹击乱码的直接表现读出来中文变成问号或横杠写进去的数据变空、乱码或者报 ORA-12899。常见原因有三个。第一数据库字符集是 AL32UTF8客户端 NLS_LANG 却是 AMERICAN_AMERICA.ZHS16GBK。OraOLEDB 会拿 NLS_LANG 做转换两边对不上就乱。第二Provider 用了 MSDAORA它内部经常把客户端字符集当作 WE8MSWIN1252无论如何都乱所以生产环境应该避免 MSDAORA。第三程序里没有走宽字符直接用 char* 传入_bstr_t在 MBCS 系统里走了错误的代码页。排查顺序建议这样命令行先set NLS_LANGAMERICAN_AMERICA.AL32UTF8再用 sqlplus 跑同一句 SQL。sqlplus 正常说明 Oracle 网络和字符集没问题问题在 ADO 层sqlplus 也乱就先解决客户端环境。set NLS_LANGAMERICAN_AMERICA.AL32UTF8 set TNS_ADMIND:\Oracle\product\11.2.0\client_1\network\admin tnsping ORCL如果是 Windows 服务里跑的程序光在命令行 set 没用要到系统环境变量改或者代码里提前调用_putenv_s(NLS_LANG, ...)。注意必须在第一次建立 Oracle 连接之前调用晚了就会失效。5. OracleADO 联调避坑驱动注册、监听服务与 32/64 位架构不匹配5.1 “未找到提供程序”和“未指定的错误”多半是 OraOLEDB 没生效现象Open 返回失败错误对话框提示 0x800A0E7A 或一句笼统的“未指定的错误”用 HRESULT 排查有时看到 0x80040154Class not registered。原因OraOLEDB.Oracle 这个 COM 组件没有在系统里注册。它属于 Oracle Client 的独立组件不是装完 Oracle 数据库就有。MSDAORA 随 Windows 自带OraOLEDB 必须要额外安装。解决安装 Oracle Client 时选择 “Oracle OLEDB Provider” 组件如果已经装好但没注册用 regsvr32 手工注册 Oracle 安装目录下的 OraOLEDB.dllregsvr32 D:\app\client\product\11.2.0\client_1\bin\OraOLEDB.dll这里还有一个隐藏问题如果你同时装了 32 位和 64 位 Oracle Client注册表里会有两套 OraOLEDB。Visual Studio 调试时如果工程是 x86 进程会去查 32 位注册表Provider 只在 32 位实例下有效。我一般建议直接编译成 x64跟目标服务器架构对齐省掉后面一堆事。5.2 连接卡住十几秒然后报 ORA-12541监听和网络名一起查现象调用 Open 后线程阻塞在超时时间附近最后返回 ORA-12541: TNS:no listener有时是 ORA-12170 连接超时。原因服务端的 TNS 监听没有启动或 tnsnames.ora 里指向的主机、端口、服务名不对。解决不要急着改代码先在命令行里做三件事lsnrctl status tnsping ORCL sqlplus scott/tigerORCL如果 lsnrctl 说 no listener到 Windows 服务里启动 OracleOraDb11g_home1TNSListener。如果监听一直起不来打开%ORACLE_HOME%\network\log\listener.log看 1521 端口是否被占用。清理监听日志时不要直接删正在被写着的 listener.log把原文件改名 listener.log.old再用 lsnrctl 重启监听否则句柄冲突会让你误以为监听又坏了。5.3 32/64 位与 VC 运行库换台机器就抛“没有注册类”现象开发机跑得很好打包给现场机器后一连接就异常退出事件查看器里是 0xc0000409或者启动直接报“应用程序无法启动因为应用程序配置不正确”。原因常见是两个叠加。一是开发机装的是 64 位 Oracle Client而打包出来的进程是 32 位二是生产机缺少 Visual C Redistributable 运行库OraOLEDB.dll 本身依赖 VC 运行库缺了连注册都不成功。解决进程位数和 Provider 位数必须一致。Visual Studio 工程是 x86就装 32 位 Oracle Client如果已经决定用 64 位 Instant Client就把工程改成 x64。部署机上提前把 VC 2015-2022 Redistributablex86/x64 对应版本装好再注册 OraOLEDB。注册失败时优先怀疑缺运行库而不是 Oracle Client 装坏了。还有一个与 vscode 配置 c/c 环境相关的小点如果你在 vscode 里用 MSVC 工具链编译#import msado15.dll默认能找到路径但如果你用 MinGW-GCC 编译它不认识#import指令得改用#include adoid.h或手工声明 COM 接口。另一个容易漏的是编译选项必须带/EHsc否则#import生成的智能指针在异常处理上不对运行期 new 对象时可能崩。5.4 中文乱码反复出现NLS_LANG 改了却没生效的三种情况现象代码里用_putenv_s设置了 NLS_LANG再连 Oracle 还是乱码。原因第一种_putenv_s调用发生在CoInitialize之后OLE DB Provider 已经在进程里加载过一次会话级 NLS 设置不再生效。第二种Windows 服务程序的默认环境变量和交互式终端不一样命令行 set 只影响当前终端。第三种ODBC 或 MSDAORA 有自己的字符集处理逻辑不完全听 NLS_LANG 的。解决设 NLS_LANG 必须放在任何 Oracle 调用、任何 COM 初始化之前。实在无效时在 CAdoDatabase::Open 成功后执行一句ALTER SESSION SET NLS_LANGUAGEAMERICAN强制会话端语言。对某一列乱码不依赖 ADO 转码直接在 SQL 里用TO_CHAR控制输出格式这是最简单也最稳的后悔药。6. CAdoDatabase 最后一块拼图超时控制、错误定位与连接复用6.1 超时到底怎么设置ConnectionTimeout 不等于能杀掉慢查询我在第 2 章的骨架里同时设置了 ConnectionTimeout 和 CommandTimeout这两个作用域完全不同。ConnectionTimeout 管建立网络连接阶段握手、监听、认证都算CommandTimeout 管每次 Open/Execute 执行命令的阶段。如果一个存储过程本身就跑 60 秒你设 15 秒 CommandTimeout到点就抛 Command timeout。需要单独提供 SetCommandTimeoutvoid CAdoDatabase::SetCommandTimeout(long nSeconds) { if (m_pConnection ! NULL) { m_pConnection-CommandTimeout nSeconds; } }注意这个超时不是万能的Oracle 端如果被锁等待卡死ADO 只是抛异常数据库里的会话可能还在挂起。真要杀会话得配合ALTER SYSTEM KILL SESSION或者程序里调底层接口。6.2 错误日志从 Errors 集合里挖 ORA- 编号_com_error::ErrorMessage()给的信息经常很笼统真正有价值的是连接对象的 Errors 集合。每次 catch 后把集合里的 Description 拼起来CString CAdoDatabase::CollectErrors(_ConnectionPtr pConn) { CString strErr; if (pConn NULL) return strErr; try { long nCount pConn-Errors-Count; for (long i 0; i nCount; i) { ErrorPtr pErr pConn-Errors-GetItem(i); strErr CString(pErr-Description) _T(\n); } } catch (_com_error) { } return strErr; }判断错误时看pErr-Number比如 0x80040E14 是 SQL 语法错误0x80040E37 是找不到表。Oracle 场景下 Description 里通常带 ORA- 编号直接 grep ORA- 就能定位。我自己的实践是每个方法 catch 时把函数名、HRESULT、Errors 集合拼成一行日志排障效率高很多。6.3 连接复用一个线程一个 CAdoDatabase别共享CAdoDatabase 的封装粒度决定了你可以在一个线程内长期持有一个实例。每次查询都新建连接成本太高因为 OLE DB 在 Oracle 侧还要建立会话和登录。更稳的做法是单例池或者干脆一个线程持有同一个 CAdoDatabase 对象。但长期复用会遇到网络瞬断和 Oracle 会话超时。碰到 ORA-03113end-of-file on communication channel时先把连接 Close 掉再重新 Open不要在同一连接上反复重试。每条 SQL 返回新的_RecordsetPtr是对的如果你把多个结果集缓存起来长事务和快照读的坑就会接踵而至。我早年处理过一个很刁钻的问题同一个 CAdoDatabase 被两个线程共用关闭 COM 时偶发崩溃。后来定下规则一个 ADO 连接对象绑定一个线程要么自己加锁要么每线程一个实例绝不共享 Connection。这是 ADO 的线程模型限制不是 Oracle 的限制。先看组件位数和字符集再查连接复用这类问题大多能少走一半弯路。希望这篇文章能让你更快把 CAdoDatabase 这类封装吃透也少在同样地方翻车。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询