環(huán)境避坑指南)
簡介適用于Delphi和CBuilder開發(fā)者的ODAC組件包Oracle Data Access Components用于在VCL/FMX應(yīng)用中快速連接并操作Oracle數(shù)據(jù)庫。核心包含TOracleConnection管理物理連接TOracleQuery與TOracleTable執(zhí)行查詢和數(shù)據(jù)維護TOracleTransaction控制事務(wù)可覆蓋從基礎(chǔ)增刪改查到存儲過程調(diào)用、異步操作等常見開發(fā)需求尤其適合需要直接訪問Oracle的中高級桌面應(yīng)用開發(fā)者。資源共1807個文件壓縮包約9.7MB。以pas源碼、dcu編譯單元、dfm窗體設(shè)計、dpk包文件為主另有大量bmp圖標、sql腳本、配置文件、編譯批處理及示例工程便于理解組件結(jié)構(gòu)、查閱接口定義并快速搭建測試環(huán)境。當前已有467人學(xué)習(xí)適合對照示例掌握ODAC的連接配置、數(shù)據(jù)綁定與性能優(yōu)化技巧。1. ODAC 是什么Delphi 項目換掉 BDE 之后Oracle 連接靠它接住Delphi 連 Oracle繞不開連接控件這一層。早年用 BDE 的項目重裝系統(tǒng)要重配別名換臺機器經(jīng)常連不上后來改用 ADO又得先裝 OLEDB 驅(qū)動。ODAC 是專注 Oracle 的 Delphi 連接控件TOraSession 管連接、TOraQuery 跑 SQL部署不用裝額外數(shù)據(jù)庫引擎環(huán)境依賴少了一大截。ODAC 最實用的是直連模式不依賴本機安裝 Oracle 客戶端桌面程序交付時少很多折騰。代價是要弄懂 SID、Service Name、字符集這些概念寫錯一個字符就報 ORA-12154。這篇寫給接手 Delphi Oracle 維護項目的開發(fā)也寫給新項目選型想評估 ODAC 值不值得用的人。下面按落地順序講組件選型、最小代碼、生產(chǎn)參數(shù)、翻車現(xiàn)場。2. 組件架構(gòu)與連接串TOraSession 的職責和 Server 參數(shù)的三種寫法ODAC 整套組件里真正高頻的只有四個先分清職責再動手寫代碼后面排查問題會快很多。連接掉了、事務(wù)沒提交、界面刷不出來分別對應(yīng)不同組件的配置搞混了只能瞎試。2.1 四個核心組件TOraSession、TOraQuery、TOraDataSource、TOraScriptTOraSession 是連接持有者一個程序通常只保留一個實例負責建立連接、提交回滾事務(wù)、維護會話狀態(tài)。TOraQuery 是 SQL 執(zhí)行器查詢、插入、更新、刪除都走它用它之前把 SQL 文本賦進去再綁定參數(shù)。TOraDataSource 是結(jié)果集和界面控件之間的橋把 TOraQuery 的數(shù)據(jù)交給 DBGrid、DBEdit 顯示。TOraScript 用來執(zhí)行一段連續(xù) SQL建表、初始化數(shù)據(jù)、跑遷移時會用到。組件選型上我一般守一條原則90% 的界面查詢用 TOraQuery TOraDataSource 就夠別圖省事直接拖 TOraTable。TOraTable 適合做單表維護工具但表結(jié)構(gòu)一復(fù)雜它的映射規(guī)則會讓人頭痛而且不小心會把整表數(shù)據(jù)拉進內(nèi)存。TOraScript 也不是常駐組件初始化腳本跑完就可以從窗體上刪掉。這里有一個容易忽略的關(guān)系TOraQuery 必須指定 Session 屬性指向 TOraSession 實例否則運行時直接報會話未賦值。一個 TOraSession 可以掛任意多個 TOraQuery它們共用同一條物理連接這也是程序里只保留一個 TOraSession 實例的原因。設(shè)計期配置可以雙擊 TOraSession 調(diào)出連接編輯器填好賬號密碼點 Connect 就能預(yù)覽連通性這個編輯器對新手指路很有用。但項目一旦要交付連接參數(shù)建議放進運行時代碼最好從配置文件讀取否則每個客戶環(huán)境變了都得重編譯。2.2 Server 參數(shù)的三種形態(tài)TNS 別名、直連 SID、直連 Service Name連接信息集中在 TOraSession 的屬性上。UserName 和 Password 對應(yīng)數(shù)據(jù)庫賬號Port 默認 1521這些都好理解。最容易出錯的是 Server 屬性它有三種取值形態(tài)Server 寫法示例前置條件適用場景TNS 別名ORCL需要 tnsnames.ora 且能被 ODAC 找到沿用公司既有 TNS 配置直連 SID192.168.1.10:1521:ORCL無Oracle 11g 及更早版本直連 Service Name192.168.1.10:1521/ORCLPDB無Oracle 12c 及以后的 PDBTNS 別名看起來很省事但在直連模式下根本不生效。Options.Direct 為 True 時ODAC 不會去解析 tnsnames.oraServer 必須寫成 host:port:sid 或 host:port/service_name 的完整形式。第二個容易踩的是 12c 以后的多租戶架構(gòu)默認建的是 CDB 和 PDB連 PDB 要用 Service Name也就是斜杠寫法如果照舊用冒號寫法填 SID大概率報 ORA-12154。所以我的習(xí)慣是新項目一律用 host:port/service_name并把這段配置寫在 INI 里代碼里只做讀取和賦值OraSession1.Options.Direct : True; OraSession1.Server : Config.ReadString(DB, Server, 192.168.1.10:1521/ORCLPDB); OraSession1.UserName : Config.ReadString(DB, UserName, APP_USER); OraSession1.Password : Config.ReadString(DB, Password, );代碼邏輯不復(fù)雜先把直連模式打開Server 用斜杠格式指向 Service Name賬號密碼從配置讀取。這里有個細節(jié)Password 不要硬編碼在代碼里也不要出現(xiàn)在編譯產(chǎn)物中配置文件本身的讀權(quán)限要收緊否則數(shù)據(jù)庫賬號等于裸奔。如果配置文件缺失或讀出來是空串我一般會在 Connect 前做一次顯式檢查把「數(shù)據(jù)庫服務(wù)器未配置」這類中文提示拋給操作員而不是讓系統(tǒng)彈一個英文 ORA 錯誤。這個習(xí)慣能減少大量無謂的工單。提示設(shè)計期用連接編輯器調(diào)試時Object Inspector 里填的 Server 格式和運行時代碼要保持一致。很多人設(shè)計期用 TNS 別名連上了運行時代碼卻按直連寫結(jié)果設(shè)計期正常、運行期報錯這類問題排查起來很繞。3. 最小可運行代碼ODAC 建立連接、參數(shù)查詢、事務(wù)提交的三段式連接串和組件關(guān)系理清之后就可以寫代碼了。這一章按依賴順序給三段代碼查詢和寫入都建立在連接已建立的前提下順序不要顛倒。3.1 建立連接并處理失敗Connected 判斷與 EOraError 捕獲連接動作本身不復(fù)雜難在處理失敗。下面是我在窗體按鈕事件里常用的寫法也可以直接抽成公共函數(shù)procedure TMainForm.ConnectDB; begin // 已連接就直接返回避免重復(fù)建連 if OraSession1.Connected then Exit; OraSession1.ConnectTimeout : 10; // 單位秒生產(chǎn)環(huán)境必須限制 try OraSession1.Connect; except on E: EOraError do begin LogError(Format(連接失敗錯誤碼 %d%s, [E.ErrorCode, E.Message])); raise; // 不要吞異常讓上層知道失敗 end; end; end;說明幾處細節(jié)。Connected 判斷在按鈕被連續(xù)點擊時很有用否則每次點擊都觸發(fā)一次建連數(shù)據(jù)庫側(cè)會話會快速堆積。ConnectTimeout 默認是 0表示無限等待生產(chǎn)環(huán)境必須給一個有限值我一般給 10 秒否則數(shù)據(jù)庫不可達時程序會卡在連接動作上像死機一樣。EOraError 是 ODAC 的統(tǒng)一異常類ErrorCode 就是 ORA- 后面的數(shù)字也就是 12154、12541 這類記日志時務(wù)必帶上。最后那個 raise 是關(guān)鍵很多問題看起來像「沒反應(yīng)」其實是異常被吞了。如果要做斷線重連我見過最可靠的做法不是依賴某個組件事件而是把 ConnectDB 抽成公共函數(shù)在每次 ExecSQL 或 Open 之前調(diào)用一次。調(diào)用成本很低因為內(nèi)部有 Connected 判斷只有在斷線時才會真正重建連接這個模式在客戶端程序里比任何自動重連配置都直觀。3.2 參數(shù)化查詢綁定變量寫法與結(jié)果集遍歷查詢用 TOraQuerySQL 文本里用冒號標記參數(shù)OraQuery1.Session : OraSession1; OraQuery1.SQL.Text : SELECT EMP_NO, EMP_NAME, DEPT_ID FROM EMP WHERE DEPT_ID :DEPT_ID ORDER BY EMP_NO; OraQuery1.ParamByName(DEPT_ID).AsInteger : 10; OraQuery1.Open; try while not OraQuery1.Eof do begin LogInfo(OraQuery1.FieldByName(EMP_NAME).AsString); OraQuery1.Next; end; finally OraQuery1.Close; // 釋放游標防止會話堆積 end;這里第一條要記住的是 Session 屬性必須賦值忘了的話運行時會提示 TOraQuery 沒有指定會話。參數(shù)用 :DEPT_ID 寫然后通過 ParamByName 賦值這是 Oracle 綁定變量的標準姿勢。千萬別用字符串拼 SQL 的方式既容易被注入又會因為每個查詢條件不同導(dǎo)致數(shù)據(jù)庫反復(fù)硬解析并發(fā)一高 shared pool 先撐不住。Open 和 ExecSQL 要分清Open 用于查詢并把結(jié)果集加載進來ExecSQL 用于執(zhí)行不返回結(jié)果集的 INSERT、UPDATE、DELETE 語句兩者混用會在運行時暴露「數(shù)據(jù)集未打開」或游標狀態(tài)錯亂的錯誤。循環(huán)里 FieldByName 按字段名取元數(shù)據(jù)方便但循環(huán)上萬次時有損耗如果數(shù)據(jù)量大、性能敏感可以把 FieldByName 提出循環(huán)或改用 Fields[0] 按索引訪問。finally 里的 Close 是習(xí)慣也是防止游標泄漏的關(guān)鍵和數(shù)據(jù)庫連接一樣不釋放的游標攢到一定數(shù)量同樣會拖垮會話。3.3 事務(wù)寫入Commit、Rollback 與異常傳播寫入操作要包事務(wù)原則很簡單要么全部成功要么全部回滾OraSession1.StartTransaction; try OraQuery1.SQL.Text : UPDATE EMP SET SALARY SALARY * :RATIO WHERE DEPT_ID :DEPT_ID; OraQuery1.ParamByName(RATIO).AsFloat : 1.1; OraQuery1.ParamByName(DEPT_ID).AsInteger : 10; OraQuery1.ExecSQL; OraQuery1.SQL.Text : INSERT INTO OP_LOG(OP_TIME, OP_TYPE) VALUES(SYSDATE, :OP_TYPE); OraQuery1.ParamByName(OP_TYPE).AsString : UPDATE_SALARY; OraQuery1.ExecSQL; OraSession1.Commit; except OraSession1.Rollback; raise; end;StartTransaction 和 BeginTransaction 是同義方法選哪個看團隊習(xí)慣。事務(wù)內(nèi)多個 SQL 共用一個連接所以 TOraQuery 不需要新建實例改 SQL.Text 和參數(shù)即可。Commit 之前任何一個步驟拋異常都會跳進 except 分支回滾然后 raise 把原始異常繼續(xù)往外拋界面層收到后可以彈中文提示。這里有個血淚經(jīng)驗回滾之后不要只彈個對話框就完事一定要讓異常繼續(xù)傳播否則調(diào)用方不知道寫入失敗了界面顯示成功但數(shù)據(jù)沒進去這種問題最難查。事務(wù)保持短是另一個原則。不要在事務(wù)里放循環(huán)執(zhí)行大量更新事務(wù)越長鎖的持有時間和 undo 累積量越大并發(fā)場景下很容易阻塞其他會話。批量寫入建議先算好批次每批一個事務(wù)處理一批提交一次。4. 生產(chǎn)環(huán)境必調(diào)的三個參數(shù)直連模式、連接池、字符集代碼跑通只是第一步生產(chǎn)環(huán)境是否穩(wěn)定取決于連接層的參數(shù)怎么設(shè)。這一章說我每次上線前都會檢查的三個項順序正好對應(yīng)部署、并發(fā)、中文數(shù)據(jù)三個場景。4.1 直連模式與客戶端模式性能與排查成本的取舍Options.Direct 是第一個要決定的參數(shù)它決定整個連接層要不要依賴外部客戶端對比項DirectTrueDirectFalse部署依賴無 Oracle 客戶端需要安裝 Oracle 客戶端或 Instant Client連接速度更快略慢多一層客戶端協(xié)議棧功能覆蓋覆蓋常規(guī)業(yè)務(wù)場景覆蓋全部客戶端特性排查難度只看 ODAC 日志需要同時查客戶端日志我的建議是純桌面應(yīng)用、數(shù)據(jù)庫走內(nèi)網(wǎng)地址的直接用 DirectTrue省掉客戶端等于省掉一半的部署問題。DirectFalse 的場景主要是使用集群特性、需要 TNS 負載均衡、或者依賴客戶端側(cè)高級配置時。直連模式不是萬能遇到集群環(huán)境先驗證一下當前 ODAC 版本對集群特性的支持程度再決定這個驗證不能省。這里再補一個判斷方法如果部署目標機器上已經(jīng)由運維統(tǒng)一裝好了 Oracle 客戶端且公司規(guī)范要求所有應(yīng)用走 TNS 別名那就用 DirectFalse 順著規(guī)范走如果目標機器環(huán)境不可控比如客戶現(xiàn)場、門店電腦優(yōu)先 DirectTrue。本質(zhì)是選一個你知道所有變量的方案。4.2 連接池與會話管理防止連接堆積程序里如果頻繁 Connect、Disconnect數(shù)據(jù)庫側(cè)的會話建立和銷毀開銷不小還把 process 數(shù)拉得很高。ODAC 提供連接池常見配置如下OraSession1.PoolingOptions.Pooling : True; OraSession1.PoolingOptions.MinPoolSize : 2; OraSession1.PoolingOptions.MaxPoolSize : 20; OraSession1.PoolingOptions.ConnectionLifetime : 600;Pooling 打開后物理連接會被復(fù)用Disconnect 只是把連接還給池子而不是真正關(guān)閉。MinPoolSize 讓系統(tǒng)啟動后就保持兩條熱連接避免首個請求等建連MaxPoolSize 是硬上限防止失控代碼把連接數(shù)打滿ConnectionLifetime 單位是秒連接使用超過這個時長就銷毀重建避免數(shù)據(jù)庫側(cè)會話資源老化。不同版本 ODAC 的屬性樹略有差異以你安裝版本的 Object Inspector 為準但思路一致。連接池和直連模式之間有個坑部分舊版本在直連模式下連接池支持不完整表現(xiàn)是開池后連接反而變慢或報錯。遇到這種情況先用一個最小 Demo 驗證池化在直連模式下是否正常再決定要不要退回客戶端模式。4.3 字符集中文亂碼與字段超長的源頭字符集問題不是偶發(fā)是配置出來的。連接建立后ODAC 默認的客戶端字符集不一定和數(shù)據(jù)庫一致中文亂碼就在這一步埋下OraSession1.Options.Charset : AL32UTF8;這個設(shè)置告訴 ODAC 用 UTF-8 與應(yīng)用交換數(shù)據(jù)。上線前可以用一條 SQL 確認數(shù)據(jù)庫的語言環(huán)境SELECT USERENV(LANGUAGE) FROM DUAL;返回結(jié)果形如 SIMPLIFIED CHINESE_CHINA.AL32UTF8看到 AL32UTF8 就與上面的設(shè)置對齊。老庫如果是 ZHS16GBK 字符集Options.Charset 也填 ZHS16GBK不要混用混用的直接后果是查詢出來中文正常寫入時字段長度不夠報 ORA-12899。這里有一個容易被忽略的坑AL32UTF8 下 VARCHAR2(N) 的長度單位是字節(jié)一個中文占 3 字節(jié)。VARCHAR2(20) 在 UTF-8 庫里只能存 6 個漢字字段設(shè)計時就要按字節(jié)留冗余否則用戶輸入稍微長一點的中文就報錯。字符集這種事沒有后悔藥上線后發(fā)現(xiàn)亂碼只能全鏈路排查所以在連接參數(shù)這一層定死它是成本最低的防線。5. ODAC 連接 Oracle 的避坑清單5 個高頻翻車現(xiàn)場與排查路徑這一章直接給結(jié)論每一條按現(xiàn)象、原因、解決三段寫。都是我在維護項目里踩過的按出現(xiàn)頻率排從最常遇到的說起。5.1 ORA-12154連接標識符解析失敗現(xiàn)象程序啟動連接時報 ORA-12154錯誤文本是 TNS:could not resolve the connect identifier specified但設(shè)計期明明連得上。原因最常見的是設(shè)計期填了 TNS 別名 ORCL連接編輯器和本地客戶端配合能解析運行時代碼把 Options.Direct 置為 True 后ODAC 不再讀 tnsnames.ora別名就變成無法解析的標識符。另一種是 Server 用冒號格式寫了 SID但 12c 以后數(shù)據(jù)庫開放的是 Service Name。解決統(tǒng)一改寫成 host:port/service_name 完整格式如果公司規(guī)范要求用 TNS 別名那就把 Direct 關(guān)掉并保證 tnsnames.ora 在 ODAC 能讀到的路徑下。出現(xiàn)這個錯先別查網(wǎng)絡(luò)先檢查 Server 字符串本身十次里有八次是格式問題。5.2 OCI.dll 加載失敗客戶端模式的環(huán)境依賴現(xiàn)象DirectFalse 時連接報錯提示找不到 OCI.dll 或 Oracle 客戶端初始化失敗同一套程序在不同機器上表現(xiàn)還不一樣有的報錯有的直接閃退。原因程序是 32 位進程而目標機器只裝了 64 位 Oracle 客戶端或者客戶端目錄沒有寫進 PATH進程根本找不到 OCI 接口庫。這類問題的麻煩在于每臺機器的 Oracle 家目錄、PATH 都不一樣排查像是開盲盒。解決要么在目標機器裝 32 位 Instant Client把目錄寫進 PATH要么放棄客戶端模式改用 DirectTrue。桌面交付場景我強烈推薦后者少一個環(huán)境依賴就少一類工單。5.3 中文亂碼與 ORA-12899字符集不匹配的連鎖反應(yīng)現(xiàn)象查詢出來的中文是問號或者插入中文時報 ORA-12899 value too large for column英文數(shù)字正常。原因Options.Charset 沒有設(shè)置或設(shè)置的字符集與數(shù)據(jù)庫不一致。AL32UTF8 下字段長度按字節(jié)計一個中文占 3 字節(jié)VARCHAR2(20) 存 6 個漢字就滿了。解決先跑 SELECT USERENV(LANGUAGE) FROM DUAL 查到庫的語言環(huán)境再把 Options.Charset 改成一致。字段長度在設(shè)計上要給中文字段留足冗余新表按字節(jié)數(shù) ×3 評估。已經(jīng)亂碼的數(shù)據(jù)要查是哪一層轉(zhuǎn)壞的通常從連接參數(shù)改起改完再驗證新寫入的數(shù)據(jù)。5.4 ORA-00020連接數(shù)把數(shù)據(jù)庫進程打滿現(xiàn)象系統(tǒng)正常運行幾天后突然所有連接都報 ORA-00020 maximum number of processes重啟程序只能好一陣過后又復(fù)發(fā)。原因代碼里 Open 了數(shù)據(jù)集但沒 Close或者每次操作都新建連接而不釋放數(shù)據(jù)庫側(cè)的會話只增不減直到打滿 process 上限。這類問題的特點是慢不會上線當天爆容易被誤判成數(shù)據(jù)庫故障。解決給所有查詢塊補 try/finally Close連接統(tǒng)一走 TOraSession 單例。打開連接池并設(shè) MaxPoolSize給連接數(shù)一個天花板。排查時用 v$session 按機器和程序名分組哪個來源會話多就順著代碼找哪里的泄漏。先查自己再查數(shù)據(jù)庫方向錯了會浪費很長時間。5.5 32 位與 64 位進程不匹配裝好了客戶端還是連不上現(xiàn)象同一份程序在 A 機器一切正常在 B 機器連不上報錯五花八門從 OCI 初始化失敗到內(nèi)存錯誤都有可能。原因Delphi 程序如果按 32 位編譯B 機器上只裝了 64 位 Oracle 客戶端兩邊位數(shù)對不上OCI 加載層面就失敗。這個問題在 x64 系統(tǒng)普及后尤其常見因為新裝的客戶端默認都是 64 位。解決確認程序編譯目標是 Win32 還是 Win64客戶端位數(shù)必須一致。最省心的還是直連模式DirectTrue 不加載外部 OCI位數(shù)問題直接消失。這也算我推薦直連的另一個理由少一堆系統(tǒng)層面的玄學(xué)。6. 把連接耗時與錯誤碼寫進日志一個提前暴露隱患的驗證習(xí)慣環(huán)境裝好、參數(shù)調(diào)完怎么驗證這套配置是可靠的我現(xiàn)在的習(xí)慣是每次連接都記錄耗時和錯誤碼把連接從黑匣子變成可審計的數(shù)據(jù)。別小看這一步它能提前暴露網(wǎng)絡(luò)抖動、監(jiān)聽隊列堆積、數(shù)據(jù)庫負載這些在界面上看不出來的問題。6.1 用 GetTickCount 記錄連接耗時uses Windows, SysUtils; var startTick, elapsedMs: DWORD; begin startTick : GetTickCount; try OraSession1.Connect; elapsedMs : GetTickCount - startTick; LogInfo(數(shù)據(jù)庫連接成功耗時 IntToStr(elapsedMs) ms); except on E: EOraError do begin elapsedMs : GetTickCount - startTick; LogError(數(shù)據(jù)庫連接失敗錯誤碼 IntToStr(E.ErrorCode) 耗時 IntToStr(elapsedMs) ms E.Message); raise; end; end; end;GetTickCount 在 Delphi 7 到新版都可用兼容老項目。局域網(wǎng)內(nèi)正常連接耗時一般在幾十毫秒如果首次連接超過 1 秒說明網(wǎng)絡(luò)質(zhì)量、監(jiān)聽隊列或數(shù)據(jù)庫負載有問題。這個日志的價值在于積累基線哪天連接變慢對比基線就能定位是數(shù)據(jù)庫側(cè)還是網(wǎng)絡(luò)側(cè)不用猜。6.2 錯誤碼速查日志里最該留下的字段連接日志里除了時間一定要保留 ORA 錯誤碼。下面是幾個高頻碼可以直接做成代碼注釋或運維手冊O(shè)RA 錯誤碼含義優(yōu)先排查ORA-12154TNS 解析失敗Server 格式、tnsnames.oraORA-12541無監(jiān)聽監(jiān)聽狀態(tài)、防火墻、端口ORA-01017用戶名或密碼無效憑據(jù)配置ORA-00020進程數(shù)超限連接池、會話泄漏ORA-12899字段超長字符集、字段長度血淚教訓(xùn)是不要只把 E.Message 寫進日志。ORA-12541、ORA-12154 這類錯誤消息本身是英文長句同一個錯誤碼能派生好幾種文本只有錯誤碼是穩(wěn)定的拿它做統(tǒng)計和關(guān)鍵字檢索才可靠。日志里有了錯誤碼客戶把報錯截圖發(fā)過來你一眼就能定位方向不用反復(fù)遠程確認?,F(xiàn)在任何一個新環(huán)境部署完我第一件事就是連一次、看日志里的耗時和錯誤碼再決定要不要交付。這個習(xí)慣幫我擋掉過好幾次「明明能連上但客戶環(huán)境一跑就崩」的尷尬。希望幫到你。本文還有配套的精品資源點擊獲取