端鏈路解析)
我最早遇到這個報錯是在一次版本發(fā)布前的數(shù)據(jù)訂正窗口。Navicat連測試庫連了一整天都好好的突然某一次點擊連接直接彈了ORA-01012: not logged on當時第一反應(yīng)是數(shù)據(jù)庫是不是被誰關(guān)掉了。結(jié)果登到服務(wù)器上看監(jiān)聽、看進程全都正常數(shù)據(jù)庫也開著。后來折騰了一圈才發(fā)現(xiàn)這個錯誤遠不像它字面上那么簡單——它只是一個包裝出來的結(jié)果真正的根因藏在服務(wù)端的事件日志里。這篇文章就把我?guī)状翁幚鞳RA-01012的完整排查思路、驗證步驟和一些容易忽略的細節(jié)寫出來。如果你是開發(fā)、測試、數(shù)據(jù)分析崗平時用Navicat連Oracle比較多遇到這個報錯時不知道怎么下手可以參考我的排查順序。內(nèi)容不涉及高深理論每一步都是能直接照著操作的。1. 先搞清楚ORA-01012到底在說什么一個被包裝過的錯誤先說結(jié)論ORA-01012全稱是not logged on直譯過來就是當前會話未登錄。但這個報錯出現(xiàn)在Navicat的連接窗口里和你直接用SQL*Plus登錄時報的同名錯誤含義并不完全一樣。在很多情況下Navicat把服務(wù)端返回的真實錯誤碼包裝成了ORA-01012返回給客戶端。換句話說你看到的這個錯誤真正的觸發(fā)原因可能藏在更底層的事件跟蹤里。這一點非常重要因為它決定了排查方向——如果你一直在客戶端層面打轉(zhuǎn)可能折騰半天都找不到根因。1.1 什么時候最容易觸發(fā)這個錯誤結(jié)合我自己遇到的場景ORA-01012最常在下面這幾種情況里冒出來數(shù)據(jù)庫實例處于啟動的中間狀態(tài)。比如執(zhí)行了startup mount或者startup nomount數(shù)據(jù)庫還沒完全open此時客戶端連進來就可能收到這類錯誤。數(shù)據(jù)庫正在執(zhí)行shutdown或者剛執(zhí)行完shutdown但監(jiān)聽器的服務(wù)注冊還沒刷新過來客戶端剛好在這個時間窗口去連。遠程連接時數(shù)據(jù)庫服務(wù)端的sqlnet.ora、tnsnames.ora配置不對或者Oracle Net Service異常終止了會話。服務(wù)器內(nèi)存壓力大、會話數(shù)達到上限導(dǎo)致已有的后臺進程被意外終止新建連接自然也進不來。Navicat所連接的Oracle賬號被鎖、口令過期但服務(wù)端返回的錯誤被客戶端包裝成了ORA-01012。你看光賬號被鎖這個原因從字面上就和not logged on八竿子打不著。所以如果你按字面去理解這個報錯很容易鉆進死胡同。1.2 為什么Navicat會把真實錯誤吞掉Navicat連接Oracle走的是OCI驅(qū)動Oracle Call Interface它在建立連接時會先和數(shù)據(jù)庫服務(wù)端做一次會話協(xié)商。如果協(xié)商階段失敗OCI層返回的錯誤碼可能并不是最原始的服務(wù)端錯誤——尤其是數(shù)據(jù)庫實例沒有完全就緒、或者監(jiān)聽器狀態(tài)異常時Navicat只能拿到一個會話建立失敗的通用錯誤最終就表現(xiàn)成了ORA-01012。這不是Navicat本身的bug而是Oracle客戶端驅(qū)動在處理非標準會話狀態(tài)時的正常行為。理解這一點之后你應(yīng)該就能想到與其糾結(jié)這個錯誤碼本身不如換個思路去服務(wù)端把真正的錯誤事件挖出來。2. 從服務(wù)端事件跟蹤器挖出真實錯誤這是排查的關(guān)鍵一步如果你打開Navicat連接數(shù)據(jù)庫彈出的還是ORA-01012我建議你先別急著改Navicat的配置。正確的下一步是去看數(shù)據(jù)庫服務(wù)器上的事件跟蹤器SQL Trace / Event Log。這一步能把被包裝的真實錯誤暴露出來。2.1 找到事件跟蹤器的位置事件跟蹤器是Oracle自帶的一個圖形化工具通常在Oracle客戶端安裝目錄下。最典型的是在開始菜單里找Oracle - OraClientXX_home下面的配置和移植工具或集成管理工具里面有個名字帶事件跟蹤器Event Tracker的入口。如果你安裝的是完整客戶端一般都能找到。如果你服務(wù)器上只有命令行環(huán)境沒有圖形界面也可以用另一種方式直接查看alert日志。這是我更習慣的做法因為生產(chǎn)服務(wù)器往往沒有桌面。2.2 查看alert日志定位根因alert日志一般在$ORACLE_BASE/diag/rdbms/{實例名}/{實例名}/trace/alert_{實例名}.log。用SQL*Plus或者直接登錄服務(wù)器進去執(zhí)行下面這條SQL就能找到日志目錄SELECT value FROM v$diag_info WHERE name Diag Alert;如果實例已經(jīng)接近崩潰、SQL*Plus都進不去那就用操作系統(tǒng)命令找find /u01/app/oracle -name alert_*.log 2/dev/null找到日志之后重點看最近一段時間的報錯條目。以我的經(jīng)驗最常見的幾種情況是ORA-01017: invalid username/password; logon denied賬號口令錯或者賬號被特別處理過ORA-28000: the account is locked賬號被鎖ORA-28001: the password has expired口令過期ORA-12514: TNS listener does not currently know of service requested服務(wù)名不對常見于連接串里的服務(wù)名寫錯ORA-12541、ORA-12560這類的網(wǎng)絡(luò)監(jiān)聽錯誤如果alert日志里能看到這些具體的錯誤碼那答案基本就明確了你就不用再在ORA-01012上死磕了。2.3 事件跟蹤器對比alert日志的使用場景事件跟蹤器和alert日志各有各的適用場景。事件跟蹤器更適合你在客戶端本機裝有完整Oracle客戶端的環(huán)境它能實時顯示服務(wù)端返回的每個事件alert日志則適合排查歷史問題比如數(shù)據(jù)庫在某個時間點發(fā)生過什么異常。如果是生產(chǎn)環(huán)境、或者數(shù)據(jù)庫駐留在遠程服務(wù)器上我個人更推薦直接用alert日志。因為事件跟蹤器容易有一個局限它顯示的是客戶端本地收到的錯誤如果錯誤在網(wǎng)絡(luò)層就被淡化了也未必能看到真實根因。而alert日志是服務(wù)端的官方記錄可信度最高。提示排查ORA-01012時優(yōu)先看服務(wù)端alert日志這一步可以直接省掉大量無謂的客戶端調(diào)試。3. 數(shù)據(jù)庫自身狀態(tài)檢查從監(jiān)聽器到實例的完整鏈路當你從服務(wù)端日志里找到線索之后下一步就是把整個連接鏈路從頭到尾過一遍。我把這個檢查順序總結(jié)成先實例、再監(jiān)聽、再賬號三步每一步都有對應(yīng)的驗證命令。按照這個順序走基本能覆蓋80%以上的原因。3.1 檢查實例狀態(tài)和數(shù)據(jù)庫開放狀態(tài)先確認實例的狀態(tài)。用系統(tǒng)管理員賬號登進數(shù)據(jù)庫或者用sqlplus以sysdba身份進去執(zhí)行SELECT status FROM v$instance; SELECT open_mode FROM v$database;正常情況下第一個查詢應(yīng)該返回OPEN第二個查詢應(yīng)該返回READ WRITE。如果第一個查詢返回的是MOUNTED或STARTED說明實例還沒完全啟動完畢連接進來自然會出現(xiàn)not logged on之類的錯誤。這里有一個特殊情況如果數(shù)據(jù)庫是用startup upgrade方式啟動的、或者正處于遷移狀態(tài)open_mode可能是READ WRITE之外的異常值。比如CONVERT、MIGRATE這些中間狀態(tài)此時客戶端連入也會報ORA-01012或類似的錯誤。遇到這種情況先確認license、遷移進程是否結(jié)束再正常重啟一次實例。3.2 監(jiān)聽器的檢查方法與常見假死情況實例狀態(tài)正常之后接著看監(jiān)聽器。在服務(wù)器上執(zhí)行l(wèi)snrctl status重點關(guān)注輸出里的Service部分確認你的數(shù)據(jù)庫service name是否在列表中以及狀態(tài)是否為UNKNOWN或READY。有一種非常典型的假活情況監(jiān)聽器進程還在端口也通但監(jiān)聽器已經(jīng)沒有響應(yīng)了。這種情況下你從客戶端執(zhí)行tnsping是通的因為端口能連通但真正建立會話時就會被拒絕表現(xiàn)也可能是一堆奇怪的ORA錯誤。判斷方式很簡單——你在服務(wù)器本地執(zhí)行l(wèi)snrctl status如果命令卡住不動或者返回TNS-01169: The listener has not been started這類信息說明監(jiān)聽器進程其實是掛了。處理方式也不復(fù)雜lsnrctl stop lsnrctl start如果監(jiān)聽器經(jīng)常莫名其妙假死建議檢查一下監(jiān)聽日志是否過大日志文件滿了之后監(jiān)聽器會出現(xiàn)各種詭異問題。清理監(jiān)聽日志是個體力活但很有效不過操作前記得備份。3.3 賬號鎖定、口令過期與資源限制第三步檢查你連接用到的賬號??梢杂霉芾韱T賬號執(zhí)行SELECT username, account_status, lock_date, expiry_date FROM dba_users WHERE username YOUR_USERNAME;如果發(fā)現(xiàn)狀態(tài)是LOCKED或者EXPIRED把它解鎖就好ALTER USER your_username ACCOUNT UNLOCK; ALTER USER your_username IDENTIFIED BY your_password;還有一類坑是profile里設(shè)置了IDLE_TIME或者CONNECT_TIME限制。如果你用完連接后長時間不操作會話被自動斷開此時新連接也可能報ORA-01012。檢查profile的方法SELECT resource_name, limit FROM dba_profiles WHERE profile (SELECT profile FROM dba_users WHERE username YOUR_USERNAME) AND resource_name IN (IDLE_TIME, CONNECT_TIME);如果限制太小可以調(diào)整或改用LIMITEDprofile。不過在生產(chǎn)環(huán)境我更建議從應(yīng)用層解決——讓連接池及時關(guān)閉空閑連接而不是依賴改數(shù)據(jù)庫參數(shù)。4. Navicat客戶端配置的常見坑tnsnames.ora與Oracle客戶端版本服務(wù)端都查完之后如果還是沒有定位到根因那就得回頭看Navicat這一側(cè)了。實際上我處理過的一個案例根因就出在客戶端的Oracle Net配置上服務(wù)端日志里根本沒有任何異常記錄??蛻舳伺渲眠@塊要分兩部分說。4.1 tnsnames.ora配置錯誤導(dǎo)致的ORA-01012Navicat通過OCI方式連接Oracle時最終還是要通過Oracle Net去解析服務(wù)名。如果你用的是服務(wù)名方式連接而tnsnames.ora里沒有對應(yīng)條目或者條目寫錯了就會觸發(fā)連接異常。檢查Navicat配置里填入的服務(wù)名字段和服務(wù)器上$ORACLE_HOME/network/admin/tnsnames.ora里的條目是否一致。一個典型的正確配置長這樣ORCL (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 192.168.1.10)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME orcl) ) )有個容易忽略的點SERVICE_NAME是服務(wù)名不一定是實例名。很多庫實例名叫orcl但服務(wù)名可能叫orcl.example.com具體取決于初始化參數(shù)service_names。判斷方法很簡單登錄數(shù)據(jù)庫后執(zhí)行SHOW PARAMETER service_names;然后把Navicat里填的服務(wù)名改成這個值。這個操作雖小但經(jīng)常能解決莫名其妙的連接問題。4.2 Navicat使用OCI驅(qū)動時的版本匹配問題另一個常見坑是Navicat自帶OCI驅(qū)動和Oracle服務(wù)器版本不匹配。尤其在Oracle 12c、18c、19c這種大版本上如果Navicat配置的OCI庫版本太老建立連接時也會出現(xiàn)異常。在Navicat里工具 - 選項 - 環(huán)境中可以看到OCI library的配置路徑。它默認會使用Navicat安裝目錄下的OCI庫你也可以手動指定到Oracle客戶端目錄下的oci.dllWindows或libclntsh.soLinux/macOS。我的建議是如果你機器上裝了Oracle完整客戶端優(yōu)先讓Navicat指向Oracle客戶端的OCI庫而不要用Navicat自帶的。原因很簡單Oracle客戶端和服務(wù)器同版本之間兼容性最穩(wěn)Navicat附帶的OCI庫更新頻率不一定跟得上Oracle版本節(jié)奏。這里有一個值得注意的細節(jié)Oracle官方對于版本匹配有嚴格規(guī)定高版本客戶端連低版本數(shù)據(jù)庫通常沒問題但低版本客戶端連高版本數(shù)據(jù)庫就可能出現(xiàn)意外。4.3 sqlnet.ora里容易忽略的SQLNET.AUTHENTICATION_SERVICES參數(shù)還有一個客戶端側(cè)的配置參數(shù)平時很少被注意到但在某些環(huán)境下會直接引發(fā)連接問題sqlnet.ora里的SQLNET.AUTHENTICATION_SERVICES。如果你本機的sqlnet.ora里設(shè)置了SQLNET.AUTHENTICATION_SERVICES (NONE)而數(shù)據(jù)庫本身配置了某種外部認證驗證方式不匹配連接同樣會失敗。這種問題比較隱蔽因為服務(wù)端日志里不一定有什么明顯痕跡客戶端所有參數(shù)看起來又都正常。處理方式也比較直接——先臨時把本機sqlnet.ora里的該參數(shù)注釋掉或者改為SQLNET.AUTHENTICATION_SERVICES (ALL)然后重啟Navicat再試一次。如果正常了說明是認證參數(shù)沖突再按實際安全策略收斂即可。注意修改后不需要重啟數(shù)據(jù)庫只需要重新連接即可。5. 真實案例復(fù)盤幾個典型根因的完整修復(fù)過程前面把整個排查框架講完了這一節(jié)我結(jié)合幾個親自處理過的案例來復(fù)盤。你會發(fā)現(xiàn)同一個ORA-01012背后的根因可能完全不一樣處理方式也大相徑庭。5.1 案例一密碼過期被當成not logged on一個生產(chǎn)庫配套的報表賬號前一天還在正常跑數(shù)據(jù)同步第二天Navicat連接直接報ORA-01012。我按老套路先去服務(wù)器上看alert日志發(fā)現(xiàn)里面清清楚楚寫著ORA-28001: the password has expired。原因也很常見Oracle 11g及以上版本默認開啟了密碼過期機制默認壽命180天應(yīng)用賬號一直沒換過密碼就過期了。解決方案很簡單把賬號密碼更新、并設(shè)置成長期有效ALTER USER report_user IDENTIFIED BY new_password;同時可以臨時把該用戶的口令過期策略調(diào)掉ALTER PROFILE app_profile LIMIT PASSWORD_LIFE_TIME UNLIMITED;這里我要多說一句生產(chǎn)中不建議一遇到密碼過期就改UNLIMITED尤其是核心業(yè)務(wù)賬號。應(yīng)該讓應(yīng)用側(cè)建立密碼周期替換機制或者用Oracle 12c及以后的Password File、AutoUpgrade這類功能來自動化處理。但如果是自己跑測試、做數(shù)據(jù)分析的賬號設(shè)成UNLIMITED問題不大省心。5.2 案例二數(shù)據(jù)庫被shutdown abort后重新啟動到半途這個案例最有迷惑性。數(shù)據(jù)庫服務(wù)異常DBA執(zhí)行了shutdown abort隨后又執(zhí)行startup。結(jié)果startup進行到一半卡住了監(jiān)聽器顯示實例狀態(tài)正常但數(shù)據(jù)庫實際還在MOUNT狀態(tài)。此時Navicat連接報的就是ORA-01012。我去服務(wù)器上執(zhí)行ps -ef | grep ora_看到進程都在執(zhí)行l(wèi)snrctl status監(jiān)聽器也正常但登錄到SQL*Plus里執(zhí)行SELECT status FROM v$instance;返回的是MOUNTED。數(shù)據(jù)庫處于mount狀態(tài)時客戶端最多只能做控制文件相關(guān)操作普通業(yè)務(wù)連接當然進不來。等ALTER DATABASE OPEN;執(zhí)行完畢Navicat再連接立刻就好了。復(fù)盤這個案例的經(jīng)驗是遇到ORA-01012第一件事不是調(diào)Navicat而是確認數(shù)據(jù)庫到底處于什么狀態(tài)。實例狀態(tài)沒確認之前其他所有客戶端操作都是浪費時間。5.3 案例三Navicat連遠程數(shù)據(jù)庫時監(jiān)聽器半死狀態(tài)還有個案例數(shù)據(jù)庫和監(jiān)聽器都正常但Navicat連接還是報ORA-01012。我反復(fù)看alert日志都沒有新記錄最后靈機一動去服務(wù)器上執(zhí)行l(wèi)snrctl status發(fā)現(xiàn)命令一直卡在Connecting to...過了很久才打印出信息。這是監(jiān)聽器半死的經(jīng)典表現(xiàn)——進程在、端口通、但無法正常處理請求。原因通常是監(jiān)聽日志文件太大超過2GB后Windows上會有問題Linux上通常沒事但也會影響響應(yīng)速度或者監(jiān)聽器線程有問題。修復(fù)方式就是重啟監(jiān)聽器lsnrctl stop lsnrctl start重啟之后Navicat立刻就能連上了。事后我把監(jiān)聽日志做了個定時清理把超過一定大小的日志歸檔壓縮之后這個庫再沒出過同類問題。提示遇到ORA-01012如果數(shù)據(jù)庫狀態(tài)、賬號狀態(tài)都正常一定記得去服務(wù)器上手動執(zhí)行l(wèi)snrctl status觀察它是否卡頓。網(wǎng)絡(luò)層面能連通不代表監(jiān)聽器健康。5.4 案例四本地OCI庫版本過舊導(dǎo)致連接協(xié)議不匹配最后一個案例是我自己本地折騰環(huán)境時遇到的。Navicat用的是自帶的OCI庫數(shù)據(jù)庫是19c結(jié)果是無論怎么配tnsnames.ora、賬號密碼絕對沒錯連上瞬間就彈出ORA-01012。后來我發(fā)現(xiàn)Navicat連接設(shè)置里默認使用的OCI庫路徑指向的是它安裝目錄下的老版本OCI把這個路徑改成Oracle客戶端安裝目錄下的oci.dll之后問題當場消失。其實原理也不復(fù)雜Oracle 19c默認的會話數(shù)據(jù)加密和認證參數(shù)比如SQLNET.ALLOWED_LOGON_VERSION_CLIENT比以前版本嚴格老版本OCI在協(xié)商階段就可能失敗。Navicat自帶的OCI庫版本如果太老就會出現(xiàn)連接報錯但看不出具體原因的情況。這個案例強烈建議大家自查一下你的Navicat用的是哪個OCI庫版本是多少如果數(shù)據(jù)庫是12c以上最好讓Navicat指向客戶端最新版Oracle的OCI庫省心不少。6. 一些值得收藏的排查命令和后續(xù)優(yōu)化思路最后再把我常用的排查命令和思路集中整理一下。這些命令我已經(jīng)用習慣了每次遇到Oracle連接類問題都從這里面找切入點。你也可以直接收藏這一節(jié)以后出問題照著做。6.1 一套完整的驗證命令序列假設(shè)你登錄到數(shù)據(jù)庫服務(wù)器上在確保賬號有權(quán)限的情況下按順序執(zhí)行以下操作查看實例狀態(tài)SELECT status FROM v$instance;返回OPEN則繼續(xù)否則先解決數(shù)據(jù)庫打開問題。查看數(shù)據(jù)庫模式SELECT open_mode FROM v$database;查看監(jiān)聽器狀態(tài)lsnrctl status注意觀察命令是否快速返回以及Service列表是否包含你的目標服務(wù)。查看賬號狀態(tài)SELECT username, account_status FROM dba_users WHERE username YOUR_USER;看alert日志最近一段時間的報錯tail -200 $ORACLE_BASE/diag/rdbms/*/*/trace/alert_*.log這五步做完絕大多數(shù)ORA-01012的根因已經(jīng)浮出水面了。剩下的無非是根據(jù)具體原因去修。6.2 防患于未然減少ORA-01012出現(xiàn)頻率的幾個習慣經(jīng)歷過幾次之后我現(xiàn)在在環(huán)境搭建時就會提前做一些設(shè)置避免后來的人再踩坑。數(shù)據(jù)庫賬號的密碼過期策略要明確開發(fā)測試庫可以直接設(shè)UNLIMITED生產(chǎn)庫走密碼周期更換流程。監(jiān)聽日志要定期輪轉(zhuǎn)日志文件過大不僅拖慢監(jiān)聽器還可能把磁盤塞滿。Linux環(huán)境下可以用logrotateWindows下寫個計劃任務(wù)按大小清理。安裝客戶端時盡量選擇與服務(wù)器主版本相同或更高的版本。不要為了省空間去用精簡版instant client完整的Oracle客戶端在排查問題時能提供更多工具。Navicat里連接Oracle之前先在服務(wù)器本地用SQLPlus測一次連接。如果SQLPlus能連上而Navicat連不上問題基本就鎖定在客戶端配置了如果兩邊都連不上優(yōu)先查服務(wù)端。6.3 如果以上方法都無效最后的兜底策略按照上面的鏈路檢查完理論上根因都能定位。但萬一你就是遇到了那種所有檢查都正常、Navicat還是報ORA-01012的情況我還有一個兜底建議直接繞過OCi配置用Navicat自帶的instant client新建一個連接模式試試。具體做法是新建連接時在連接設(shè)置里找到高級或OCI相關(guān)選項取消使用自定義OCI路徑或者換一個Oracle客戶端版本路徑。這本質(zhì)上是強制更換連接驅(qū)動實現(xiàn)很多時候能繞開OCI庫的兼容性問題。如果更換OCI路徑仍然不行那就試試用SQL*Plus或者SQL Developer(如果裝了)能否連上??蛻舳斯ぞ咧g對比連接結(jié)果能把問題快速切分到是Oracle驅(qū)動問題還是Navicat本身問題。這個切分思路在排查所有數(shù)據(jù)庫連接類問題時都通用不局限于ORA-01012。從我個人的操作習慣來說遇到ORA-01012我不再去背各種錯誤碼的意義而是直接走服務(wù)端日志-實例狀態(tài)-監(jiān)聽器-賬號狀態(tài)-客戶端OCI配置這條鏈路每一步都有明確的驗證命令。這套方法幫我處理過幾十次類似連接問題其中真正的not logged on場景其實很少大部分是被包裝過的其他原因。你下次再遇到Navicat報這個錯不用慌按這條鏈路走一遍大概率能在十分鐘內(nèi)鎖定根因。