简介这份资源是面向Delphi与CBuilder开发者的Oracle数据库连接组件包ODACOracle Data Access Components核心解决在桌面或跨平台应用中高效接入Oracle、免于深究底层数据库API的问题。包内以TOracleConnection为连接核心配合TOracleQuery、TOracleTable、TOracleTransaction及存储过程、函数组件覆盖连接池、事务控制、数据绑定、异步执行、LOB与BFILE、RAC、物化视图等进阶能力并兼容从Oracle 8i到19c的多个版本适合已具备Delphi基础、需要落地企业级数据库访问的中高级开发者参考。压缩包为rar格式共1807个文件、约9.7MB以pas源码、dcu编译单元、dfm窗体、dpk与dproj工程文件为主体另有res资源、bmp图标、xfm与nfm跨平台窗体及cfg、bat构建脚本完整呈现组件库的源码、示例与安装工程结构。目前已有471人学习下载可作为组件集成、连接参数配置与性能调优的实操参照。1. 从一次“连不上”的报错说起Delphi ODAC Oracle 连接控件到底管哪一段Delphi ODAC Oracle 连接控件本质上不是“装个驱动就能连”的黑盒而是把会话、事务、游标、连接池这些数据库资源纳入 Delphi 对象生命周期的一套数据访问层。某开发者遇到过一种很典型的情况开发机上用数据库工具能连Delphi 程序一启动就报 ORA-12154改了连接串还是时好时坏。最后定位到不是数据库挂了而是程序里同时混用了直连模式、TNS 别名和一条从旧项目抄来的连接串三套解析路径打架。这类问题最容易让人把时间耗在“数据库是不是有问题”上实际根因常在控件配置和会话释放。ODAC 适合需要精细控制 Oracle 会话、事务边界、连接池和参数化查询的 Delphi 项目如果只是临时导一张表用系统 ODBC 也能凑合但一旦要上长连接、批量写入和异常恢复控件层的边界就必须先弄清楚。2. 选型先看边界ODAC 直连、ODBC 与 Provider 在 Delphi 里的差异2.1 三条访问路径的适用面与成本Delphi 连 Oracle 常见有三条路ODAC 控件直连、ODBC 桥接、OLE DB Provider。三条路都能跑 SQL但资源控制能力差很多。ODAC 的优势是把 TOraSession、TOraQuery、TOraDataSource 这些对象放在 Delphi 进程内连接池、事务、参数绑定、字符集开关都能在控件属性里调代价是项目要引入对应控件包部署时要确认运行库和位数匹配。ODBC 的优势是系统里已有驱动就能用适合老系统或临时工具缺点是中间多一层连接串、驱动版本、字符集设置都容易和 Delphi 进程错位出问题时排查链路更长。OLE DB Provider 现在在新项目里已经不算首选维护成本高64 位和 32 位进程还经常踩坑。选型时不要只看“能不能连上”要看四件事连接是否长期存活、事务是否跨多个查询、是否需要批量提交、异常后是否要自动恢复。长连接和连接池场景优先 ODAC一次性数据迁移可以接受 ODBC如果旧项目已经深度绑定 Provider迁移前先做连接串和数据类型兼容测试。常见做法是先用 ODAC 跑一个最小查询确认监听、服务名、账号权限都通再往业务查询上扩。不要一上来就把整个数据模块换掉否则连接问题会和业务 SQL 问题混在一起变成黑匣子。2.2 最小工程放上 TOraSession 与 TOraQuery 跑通首条 SQL最小工程只放三个控件TOraSession 负责连接和事务TOraQuery 负责 SQL 和游标TOraDataSource 负责把数据集接到 DBGrid 或业务对象。设计期可以拖控件但连接参数建议在代码里显式赋值方便不同环境切换。下面这段是常见写法属性名在不同 ODAC 控件版本里可能略有差异Direct 开关和字符集选项按实际控件面板找同名或近义项。procedure TFormMain.TestOracleConnection; begin OraSession1.LoginPrompt : False; // 服务里不要弹登录框 OraSession1.ConnectString : Hostlocalhost;Port1521;ServiceNameORCL; User Idapp_user;Passwordapp_pass; OraSession1.Options.Direct : True; // 常见直连模式开关按控件版本确认 OraSession1.Options.UseUnicode : True; // 中文环境先打开 Unicode OraQuery1.Session : OraSession1; OraQuery1.SQL.Text : select 1 as ping_value from dual; try OraSession1.Connected : True; OraQuery1.Open; ShowMessage(连接成功返回值 OraQuery1.FieldByName(ping_value).AsString); finally OraQuery1.Close; OraSession1.Connected : False; end; end;这段代码把连接串、直连开关、查询对象三件事绑在一个过程里。Host 是数据库监听地址Port 默认 1521ServiceName 是服务名而不是 SIDLoginPromptFalse 防止无人值守服务卡在登录框UseUnicode 打开后后续 NVARCHAR2 和中文列读取更稳。try/finally 的顺序是先关查询再关连接避免游标还开着就归还会话。生产代码里不要把密码硬编码在连接串里建议从配置或凭据读取日志里只打印脱敏后的 Host 和 ServiceName。2.3 连接字符串、服务名与 SID 的写法对照连接串是最容易翻车的一层。有人把 SID 填进 ServiceName开发环境碰巧能连换到正式库就报监听器找不到服务。也有人同时配了 TNS 别名和 Host/Port结果程序按其中一套解析和预期不一致。下面这张表是常见写法对照实际以现场监听配置为准。写法示例适用场景注意点参数拼接Hostlocalhost;Port1521;ServiceNameORCL直连模式、容器或云上实例ServiceName 不是 SID端口要和服务端监听一致TNS 别名AliasORCL_LOCAL已有 tnsnames.ora 的老项目要确认 Delphi 进程能找到同一个 TNS_ADMINEasy Connect 风格//localhost:1521/ORCL快速验证、脚本化连接不同控件对前缀兼容性不同先跑最小查询SID 写法Hostlocalhost;Port1521;SIDORCL老库或明确知道 SID 的环境新服务名架构下容易错配优先服务名建议把连接串做成三份本机、测试、正式每份只改 Host、Port、ServiceName 和账号不把整串复制来复制去。启动时先跑select 1 from dual失败就把脱敏连接串写进日志。这样下次再遇到 ORA-12154 或 ORA-12541至少知道程序到底拿哪一串去连。3. 连接池与会话生命周期TOraSession 的 4 个关键参数怎么落3.1 连接池不是开大就好Pooling、MaxPoolSize、ConnectionTimeout 的取舍连接池解决的是“频繁创建连接”的成本不解决“会话泄漏”。很多项目一看到连接慢就把最大池调到几百结果数据库会话数被打满应用侧还以为是池不够。常见参数是 Pooling、MaxPoolSize、ConnectionTimeout不同控件版本名字可能略有差异但思路一致。Pooling 打开后Connected : False 不一定真正断开而是把会话还回池MaxPoolSize 控制单进程最多拿多少连接ConnectionTimeout 控制拿不到连接时等多久就报错。推荐起点是 MaxPoolSize 按峰值并发事务数加 20% 缓冲同时保证“应用实例数 × MaxPoolSize”不超过数据库会话上限的六到七成给运维和批处理留余量。参数常见含义建议起点调整信号Pooling是否启用连接复用长连接服务开启短任务工具可关避免池残留MaxPoolSize单进程最大池连接数2050按并发估频繁超时且数据库会话有余量再上调ConnectionTimeout等待可用连接的秒数515 秒太短会误报太长会拖住请求IdleTimeout 类参数空闲连接回收按业务峰谷设置夜里连接不释放、数据库会话不降时检查一个容易被忽略的点是连接池是进程内资源不是跨进程共享。多开客户端、多个服务实例各自持池数据库看到的是总数。估算时先算“同时有多少个请求在等数据库”再乘实例数不要只盯单机。还有一个 TOraSession 实例不要跨线程共享池解决复用不解决线程安全。多线程场景常见做法是每个线程用自己的会话或从池里取用后立刻归还不在线程间传同一个查询对象。3.2 用 try/finally 管住会话与游标一个可复用的创建函数会话泄漏最典型的画面是功能跑一天没事第二天早上连接池耗尽重启又好了。根因往往不是池小而是异常分支里 OraQuery 没 Close、事务没回滚、Connected 没归还。下面这个函数把“打开查询、执行、关闭、归还”收在一个过程里业务代码只关心 SQL 和参数。function TDataModuleMain.RunScalar(const ASQL: string; const AId: Integer): string; var Q: TOraQuery; begin Result : ; Q : TOraQuery.Create(nil); try Q.Session : OraSession1; // 复用同一会话或池 Q.SQL.Text : ASQL; Q.ParamByName(id).AsInteger : AId; // 参数名要和 SQL 里的 :id 对上 Q.Open; if not Q.IsEmpty then Result : Q.Fields[0].AsString; finally Q.Close; Q.Free; // 查询对象自己释放 end; end;这段代码的关键不是函数本身而是 finally 里的顺序先 Close 再 Free查询游标不会挂到会话上。会话本身由外层数据模块统一管理不要在每次查询里 Connected : True/False否则池的复用意义会被打断。参数用 ParamByName 绑定类型按列类型给整数列不要传字符串否则 Oracle 可能做隐式转换执行计划会变。异常往外抛时外层要负责回滚事务如果这个函数被放在事务里调用方要知道它没有 Commit。3.3 事务边界StartTransaction、Commit、Rollback 的落点事务边界要贴着业务动作不要贴着界面动作。常见错误是用户点开编辑窗口就 StartTransaction用户去泡杯茶回来再 Commit期间一直占着会话和锁。正确做法是一个业务提交点一个短事务写主表、写明细、写日志放在同一个事务里校验和界面交互放在事务外。下面是一个批量写入的骨架每批提交一次异常统一回滚。procedure TDataModuleMain.SaveBatch(const ACount: Integer); var I: Integer; begin OraSession1.StartTransaction; try for I : 1 to ACount do begin OraQuery1.SQL.Text : insert into t_order(id, order_no, amount) values (:id, :no, :amt); OraQuery1.ParamByName(id).AsInteger : I; OraQuery1.ParamByName(no).AsString : NO- IntToStr(I); OraQuery1.ParamByName(amt).AsFloat : I * 1.5; OraQuery1.ExecSQL; if (I mod 500) 0 then begin OraSession1.Commit; // 分批提交缩短锁持有时间 OraSession1.StartTransaction; end; end; OraSession1.Commit; except OraSession1.Rollback; raise; end; end;这里每 500 条提交一次是为了控制 undo 和锁的范围不是固定标准批量大小要按行宽、索引数和数据库负载调。StartTransaction 之后如果又开新事务要先确认控件是否支持嵌套很多封装并不支持真正嵌套别把 Commit 写成“提交一半”。异常分支必须 Rollback 再 raise否则外层拿到异常但事务还挂着。还有一点DBGrid 上直接编辑的数据要先 Post 到数据集再进入事务提交否则会出现“界面改了、库里没有”的假象。4. 查询控件与数据绑定TOraQuery、TOraDataSource、DBGrid 的最小闭环4.1 从 SQL 到 DBGrid字段持久化与只读列TOraQuery 打开后TOraDataSource.DataSet 指向它DBGrid.DataSource 再指向数据源这是最小闭环。设计期最容易省的一步是字段持久化双击查询控件打开字段编辑器把常用字段固定下来设置显示名、宽度和 ReadOnly。这样做的好处是 SQL 返回顺序变化时界面列不会乱坏处是加字段要回设计器改所以字段清单要跟着接口文档走。只读列建议在字段层设置不要只在 DBGrid 上设 ReadOnly否则代码里直接改字段仍然能改。procedure TFormMain.BindQuery; begin OraQuery1.Session : OraSession1; OraQuery1.SQL.Text : select order_no, customer_name, amount, status from t_order where status :status order by order_no; OraQuery1.ParamByName(status).AsString : NEW; OraDataSource1.DataSet : OraQuery1; DBGrid1.DataSource : OraDataSource1; OraQuery1.Open; OraQuery1.FieldByName(order_no).ReadOnly : True; OraQuery1.FieldByName(customer_name).ReadOnly : True; end;绑定顺序是先设 Session再设 SQL 和参数最后 OpenDataSource 和 DBGrid 可以在 Open 前设也可以在之后设但不要在 Open 过程中反复切 DataSet。order_no 和 customer_name 设为只读避免用户直接改主键和名称。如果业务需要编辑 amount要确认该列在数据库里有更新权限并且事务提交点就在保存按钮里不要依赖 DBGrid 失焦自动提交。4.2 参数化查询与批量提交别用字符串拼接换性能字符串拼接 SQL 在 Delphi 里很常见尤其是老代码SQL.Text : ... where id IntToStr(AId)。这种写法有两个问题一是注入风险二是每次文本不同Oracle 要做硬解析批量执行时性能反而更差。参数化查询把 SQL 文本固定下来只换绑定值解析计划可复用。批量插入时把参数绑定放在循环里按批次提交不要每行一次 Commit。procedure TDataModuleMain.InsertOrders(const ARows: Integer); var I: Integer; begin OraQuery1.SQL.Text : insert into t_order(id, order_no, amount, status) values (:id, :order_no, :amount, :status); OraSession1.StartTransaction; try for I : 1 to ARows do begin OraQuery1.ParamByName(id).AsInteger : I; OraQuery1.ParamByName(order_no).AsString : ORD- FormatFloat(000000, I); OraQuery1.ParamByName(amount).AsFloat : 10.5; OraQuery1.ParamByName(status).AsString : NEW; OraQuery1.ExecSQL; if (I mod 1000) 0 then begin OraSession1.Commit; OraSession1.StartTransaction; end; end; OraSession1.Commit; except OraSession1.Rollback; raise; end; end;参数名要和 SQL 里的占位符严格一致大小写通常不敏感但拼错会直接报参数找不到。AsInteger、AsFloat、AsString 要按列类型选金额列不要用浮点做精确计算生产里常见做法是整数分或定点小数。批次大小 1000 不是硬指标索引多、触发器和日志重的表要往下调提交太频繁会增加事务开销提交太少会撑大 undo。批量写入前先关掉不必要的界面刷新和日志逐条落盘但异常日志要保留。4.3 大字段与字符集CLOB、NVARCHAR2 的读取写法中文乱码和 CLOB 截断经常一起出现。原因通常有三类UseUnicode 没打开、数据库字符集和应用端解释不一致、CLOB 被当普通字符串一次性读取。先查数据库字符集再确认控件字符集选项最后按字段类型选读取方式。小文本可以用 AsWideString大 CLOB 建议用流式或分块接口具体类名看控件版本。下面这条 SQL 用来核对数据库字符集结果不要直接拼进界面只写日志。select parameter, value from nls_database_parameters where parameter in (NLS_CHARACTERSET, NLS_NCHAR_CHARACTERSET);procedure TFormMain.LoadDocument(const ADocId: Integer); begin OraQuery1.SQL.Text : select doc_id, doc_name, content from t_doc where doc_id :doc_id; OraQuery1.ParamByName(doc_id).AsInteger : ADocId; OraQuery1.Open; try if not OraQuery1.IsEmpty then begin Memo1.Lines.Text : OraQuery1.FieldByName(doc_name).AsWideString; // 大 CLOB 不要假设 AsString 一定完整按控件能力走流式读取 Memo1.Lines.Text : Memo1.Lines.Text sLineBreak OraQuery1.FieldByName(content).AsWideString; end; finally OraQuery1.Close; end; end;这段代码先读名称再读内容doc_id 用参数绑定。AsWideString 适合中小文本如果 content 列很大AsWideString 可能受内存和控件缓冲影响常见做法是拿到字段对象后用流或分块接口写入文件再让界面按需加载。字符集这边UseUnicode 打开只是第一步数据库字符集、NLS 参数、客户端环境变量要一起核对别只改一个地方就下结论。乱码问题最好在最小查询里复现用select 中文测试 from dual跑一遍能快速判断是连接层还是展示层的问题。5. 避坑排查ODAC 连接 Oracle 最常见的 5 个翻车点5.1 连接与解析类开发机能连、服务器报 ORA-12154现象同一个程序开发机上能连部署到服务器启动就报 ORA-12154数据库工具却能连。原因程序用的连接串解析路径和工具不一致。常见情况是开发机配了 TNS 别名服务器没有同一个 TNS_ADMIN或者代码里一部分走 Direct 模式一部分走客户端解析两套配置打架。解决把连接方式收敛成一种优先显式写 Host、Port、ServiceName并在启动日志里打印脱敏连接串。Direct 开关要么统一开要么统一关不要在不同数据模块里混用。服务器上先跑最小select 1 from dual再跑业务 SQL避免把权限问题误判成解析问题。现象连接串里写的是 SID测试库能连正式库报 ORA-12541 或监听器找不到服务。原因正式库可能用服务名注册SID 和 ServiceName 不是同一个概念端口或监听地址也可能不同。解决向数据库侧确认 ServiceName不要凭历史连接串猜。连接串表里把本机、测试、正式三套分开只改差异项。上线前用最小查询验证不要等业务模块全加载完才暴露。5.2 会话与事务类连接池耗尽和游标不释放现象程序跑几个小时后开始报连接超时重启后恢复数据库会话数居高不下。原因异常分支没有 Close 查询或者事务 Rollback 没执行连接没有归还池也有可能是 MaxPoolSize 开得太大把数据库会话打满。解决所有查询统一 try/finally先 Close 再 Free事务异常分支必须 Rollback池大小按“实例数 × MaxPoolSize”估算不超过数据库会话余量。加一个定时日志记录池占用和活动会话数别等用户报障才查。现象DBGrid 里改了数据保存按钮也点了刷新后部分字段没变。原因数据集没有 Post或者事务提交前界面又触发了新查询也有项目把缓存更新和事务混用ApplyUpdates 没落到同一个提交点。解决保存动作里显式Post再Commit缓存更新模式要确认 ApplyUpdates 的调用位置。事务内不要做长时间界面交互提交点贴着业务动作别贴着窗口打开。5.3 字符集与性能类乱码、慢查询与隐式转换现象中文名称显示成问号或者 CLOB 内容只出来前一段。原因UseUnicode 没开NLS 参数和数据库字符集不一致大字段被当普通字符串一次性读取缓冲不够就截断。解决先查NLS_CHARACTERSET和NLS_NCHAR_CHARACTERSET再统一下控件字符集选项小文本用 AsWideString大 CLOB 走流式或分块读取。乱码用select 中文测试 from dual做最小复现能快速分清连接层和展示层。现象参数化查询写好了批量执行还是慢执行计划里出现隐式转换。原因参数类型和列类型不匹配比如数字列传字符串、日期列传格式化文本或者 SQL 文本仍在拼接导致硬解析。解决参数按列类型赋值数字用 AsInteger/AsFloat日期用日期类型SQL 文本固定只换参数值。看执行计划时重点看有没有函数作用在索引列上以及绑定变量是否真的复用了。性能问题不要先怪 ODAC先把 SQL 文本和参数类型对齐。6. 把连接问题变成可观测数据我常用的 3 个探针连接类问题最怕变成黑匣子用户只说“又连不上了”日志里只有一句异常。我现在的习惯是先埋三个探针再写业务查询。第一个探针记录连接耗时在会话连接成功后写一条脱敏日志包含 Host、ServiceName、耗时和当前池占用第二个探针包住查询打开和关闭记录 SQL 标识、参数个数、返回行数和耗时不记录密码和完整敏感字段第三个探针做低频健康检查定时跑select 1 from dual间隔不要太密避免健康检查本身把池占满。procedure TDataModuleMain.OraSessionAfterConnect(Sender: TObject); begin // 只记录脱敏信息不打印密码和完整连接串 LogInfo(Oracle connected, elapsed IntToStr(GetTickCount - FConnectStartTick) ms); end; procedure TDataModuleMain.OraQueryAfterOpen(DataSet: TDataSet); begin LogInfo(Query opened, rows IntToStr(DataSet.RecordCount) , elapsed IntToStr(GetTickCount - FQueryStartTick) ms); end;探针的价值不在代码多而在出问题时能回答三个问题连的是哪个服务、等了多久、查询返回了多少行。连接耗时突然从几十毫秒涨到几秒先查网络和监听查询耗时涨但行数没变先看执行计划和参数类型池占用一直不降先查 finally 和事务提交点。血泪经验是别等翻车后才补日志连接串、池大小、字符集这三类信息在启动时就该落下来。我现在接 Delphi ODAC 项目先把这三个探针和最小查询跑通再动业务表后面排查会省很多后悔药。希望帮到你。本文还有配套的精品资源点击获取