3个最常见案例详解DBA日常维护
導讀:DBA的大部分工作都是圍繞著對數據庫的維護而展開的,常規的日常維護更是占了絕大多數。本節將圍繞日常維護中最常見的三個案例展開講解,與大家分享排查此類問題的思路。
作者:葉樺 徐浩 張夢穎 應以峰
來源:大數據DT(ID:hzdashuju)
01 TX鎖處理
TX鎖,也稱事務鎖或行級鎖,是控制數據庫并發訪問的一項重要技術,也是數據完整性和一致性的重要保證。本文不會過多闡述鎖的類型和具體原理,而是重點講解在生產環境中遇到鎖的時候,如何快速查找源頭并進行查殺。
有經驗的DBA在遇到TX鎖時,第一反應就是查詢v$lock和v$session視圖,定位LMODE和REQUEST類型互斥的會話并進行查殺。然而,隨著數據庫版本不斷地迭代更新,v$session視圖的內容越來越豐富,可以直接使用blocking_session、blocking_instance、final_blocking_instance和final_blocking_session字段進行定位。
對于鎖層次的排查可以重復查詢v$session來確定,但如果鎖層次有100層,那么通過人工遍歷100次的方式,顯然過于低效,不適用于生產環境。
下面就來介紹本節的主角:Oracle的SYS_CONNECT_BY_PATH函數。
自Oracle 9i開始,DBA就可以使用SYS_CONNECT_BY_PATH函數將父節點到當前行的內容以“路徑”或層次的形式顯示出來。該功能剛好符合我們遞歸查找鎖層次的需求,在這里,筆者模擬了鎖環境,可以使用如下語句查詢鎖信息:
SQL>?select?a.inst_id,a.process,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf?as?isleaf,sys_connect_by_path(a.SID?||?'@'?||?a.inst_id,?'?<-?')?tree,level?as?tree_levelfrom?gv$session?astart?with?a.blocking_session?is?not?null connect?by?(a.sid?||?'@'?||?a.inst_id)?=?prior(a.blocking_session?||?'@'?||?a.blocking_instance); <!--省略部分列--> INST_ID?PROCESS?SID??SERIAL#?EVENT?????????????????????????STATUS??ISLEAF?TREE????????????TREE_LEVEL -------?-------?----?-------?-----------------------------?-------?------?---------------?---------1????7663???17????6749?enq:?TX?-?row?lock?contention?ACTIVE???????0?<-?17@1?????????????????11????6198???25????9989?SQL*Net?message?from?client???INACTIVE?????1?<-?17@1?<-?25@1?????????21????6310???28???23199?enq:?TX?-?row?lock?contention?ACTIVE???????0?<-?28@1?????????????????11????6198???25????9989?SQL*Net?message?from?client???INACTIVE?????1?<-?28@1?<-?25@1?????????2下面對代碼段中的部分參數進行說明。
INST_ID:會話所在的節點號。
PROCESS:客戶端進程號,與v$process中的spid不是同一個。
SID、SERIAL#、SQL_ID、STATUS、PROGRAM、MACHINE:會話信息。
ISLEAF:是否為源頭,0代表否,1代表是。
TREE:樹形結構,鎖的層次,例如,<- 152@2 <- 153@2 <- 161@1,從左到右依次表示為節點2的會話152被節點2的會話153堵塞,而節點2的會話153又被節點1的會話161堵塞。所以節點1的會話161是鎖的源頭。
TREE_LEVEL:樹形層次。
鎖源頭的查殺方法有兩種,說明如下。
1)通過ISLEAF進行篩選,直接查殺鎖源頭,語句如下:
SQL>?select?'alter?system?kill?session?'''?||?sid?||?''?||?','?||?serial#?||?',@'?||inst_id?||?'''?immediate;'?db_kill_sessionfrom?(select?a.inst_id,a.process,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf?as?isleaf,sys_connect_by_path(a.SID?||?'@'?||?a.inst_id,?'?<-?')?tree,level?as?tree_levelfrom?gv$session?astart?with?a.blocking_session?is?not?nullconnect?by?(a.sid?||?'@'?||?a.inst_id)?=?prior(a.blocking_session?||?'@'?||?a.blocking_instance))where?isleaf?=?1order?by?tree_level?asc; KILL_SESSION --------------------------------------------------- alter?system?kill?session?'161,5579,@1'?immediate; alter?system?kill?session?'161,5579,@1'?immediate;SQL>?select?inst_id,?'kill?-9?'?||?spid?os_kill_sessionfrom?(select?p.inst_id,p.spid,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf?as?isleaf,sys_connect_by_path(a.SID?||?'@'?||?a.inst_id,?'?<-?')?tree,level?as?tree_levelfrom?gv$session?a,?gv$process?pwhere?a.inst_id?=?p.inst_idand?a.paddr?=?p.addrstart?with?a.blocking_session?is?not?nullconnect?by?(a.sid?||?'@'?||?a.inst_id)?=?prior(a.blocking_session?||?'@'?||?a.blocking_instance))where?isleaf?=?1order?by?tree_level?asc;?INST_ID?OS_KILL_SESSION ----------?--------------------------------1?kill?-9?300492)借助v$session中的final_blocking_instance和final_blocking_session定位鎖源頭,語句如下:
SQL>?select?'alter?system?kill?session?'''?||?ss.sid?||?''?||?','?||?ss.serial#?||?',@'?||ss.inst_id?||?'''?immediate;'?db_kill_sessionfrom?gv$session?s,?gv$session?sswhere?s.final_blocking_session?is?not?nulland?s.final_blocking_instance?=?ss.inst_idand?s.final_blocking_session?=?ss.sidand?s.sid?<>?ss.sid DB_KILL_SESSION -------------------------------------------------- alter?system?kill?session?'161,5579,@1'?immediate; alter?system?kill?session?'161,5579,@1'?immediate;SQL>?select?p.inst_id,?'kill?-9?'?||?p.spid?os_kill_sessionfrom?gv$session?s,?gv$session?ss,?gv$process?pwhere?s.final_blocking_session?is?not?nulland?s.final_blocking_instance?=?ss.inst_idand?s.final_blocking_session?=?ss.sidand?ss.paddr?=?p.addrand?ss.inst_id?=?p.inst_idand?s.sid?<>?ss.sidINST_ID?OS_KILL_SESSION ----------?--------------------------------1?kill?-9?30049執行拼接生成的語句,即可殺掉鎖的源頭。
想必大家都遇到過在數據庫層面發起“alter system kill session”(數據庫層殺掉會話,不加immediate關鍵字)時,經常會出現資源無法及時釋放、會話一直處于killed狀態的情況。
如果這個會話是鎖的源頭,那么除了等待PMON(進程監視器)來清理之外,再沒有更好的辦法了,而在操作系統層面殺掉進程的方式,基本上是百試百靈。
使用系統命令“kill -9”殺死進程,系統向該process進程發出sigkill,sigkill信號直接發送給init進程,終止process進程。這種方式直接終止了Oracle 會話中對應的操作進程,資源也可以直接釋放。
下面就來重點講解“alter system kill session”的過程,以及在“alter system kill session”殺掉會話之后,為何會查不到處于killed狀態的會話所對應的系統進程spid。
“alter system kill session”(不加immediate關鍵字)殺掉會話可分為兩種場景進行討論:會話狀態分別是active和inactive。
使用此命令殺掉處于active狀態的會話時,過程可以簡單概括如下:
會話在收到kill信號后進行回滾,此過程不可被中斷,直至過程完成,該會話會接收到“ORA-00028: your session has been killed”信息,PMON清理會話,釋放資源。如果1分鐘過后,上述動作還未完成,則該會話將被標記為killed狀態,若會話擁有的資源未釋放,則等待PMON進程清理會話。
使用此命令殺掉處于inactive狀態的會話時,過程可以簡單概括如下:
會話在收到kill信號后被標記為killed狀態,會話擁有的資源未釋放,等待PMON進程清理會話。如果會話再次發出查詢信號,會話就會接收到“ORA-00028: your session has been killed”信息,PMON清理會話,釋放資源。
接下來模擬不加immediate參數,殺掉會話后狀態被標記為killed,操作系統查不到進程的實驗場景,過程如下:
SQL>?select?username,sid,serial#,paddr,server,status?from?v$session?where?username?=?'SCOTT';USERNAME?????SID????SERIAL#?PADDR????????????SERVER????STATUS ----------?-----?----------?----------------?---------?-------- SCOTT?????????17???????6733?00000000A34C7040?DEDICATED?INACTIVE SCOTT????????158???????9177?00000000A34D4998?DEDICATED?INACTIVESQL>?select?b.sid,b.serial#,c.spid,b.status?from?v$session?b,v$process?c?where?b.paddr?=?c.addr?and?b.sid?in?(17,158);SID????SERIAL#?SPID??????STATUS ----?----------?---------?--------17???????6733?23883?????INACTIVE158???????9177?24120?????INACTIVE手動殺掉這兩個會話的命令如下:
SQL>?alter?system?kill?session?'17,6733'; SQL>?alter?system?kill?session?'158,9177';再次查詢這兩個會話的狀態,命令及結果如下:
SQL>?select?username,sid,serial#,paddr,server,status?from?v$session?where?username?=?'SCOTT'; USERNAME????SID????SERIAL#?PADDR????????????SERVER????STATUS ----------?----?----------?----------------?---------?-------- SCOTT????????17???????6733?00000000A3551F18?PSEUDO????KILLED SCOTT???????158???????9177?00000000A3551F18?PSEUDO????KILLED從代碼中我們可以發現,當兩個會話的狀態為killed時,會話的paddr指向同一地址00000000A3551F18(虛擬地址),此地址在操作系統層面并無對應的spid,這就是當會話的狀態變為killed之后,使用以下語句查不到spid的原因,查詢示例代碼如下所示:
SQL>?select?b.sid,b.serial#,c.spid,b.status?from?v$session?b,v$process?c?where?b.paddr?=?c.addr?and?b.sid?in?(17,158); no?rows?selected此時,我們就可以使用前文的查詢語句,查殺并清理會話,命令及結果如下:
SQL>?select?'alter?system?kill?session?'''?||?c.sid?||?''?||?','?||?c.serial#?||?'''immediate;'?kill_session?from?v$session?c?where?status='KILLED'; KILL_SESSION ----------------------------------------------- alter?system?kill?session?'17,6733'?immediate; alter?system?kill?session?'158,9177'?immediate;因此,在查殺會話時,可以考慮直接使用“alter system kill session 'sid,serial#' immediate”命令快速清理會話。需要注意的是,在查殺會話之前一定要再三確認信息,千萬不要誤殺了系統核心進程。
02 高峰期謹慎編譯業務對象
想必大家都遇到過這樣的情況,在業務高峰期如果編譯存儲過程、函數或視圖,就會導致大量使用該對象的會話堵塞,自身也將處于掛起狀態,后臺等待事件為“library cache pin”。
在日常運維中,“library cache”相關等待較為常見,主要分為“library cache lock”或“library cache pin”,前者維護“library object handle”上的并發訪問,后者維護“library object handle”下對應heap的并發訪問,lock管理并發,pin管理一致性。
當我們編譯存儲過程、函數或視圖的時候,Oracle就會在這些對象的handle上獲得一個“library cache lock”,然后在這些對象的heap上獲得pin,這樣就能保證在編譯的時候其他進程不會來更改這些對象。
有了以上的理論基礎,當高峰期編譯對象出現會話堵塞的問題時,我們應該如何處理呢?這里就會用到基表DBA_KGLLOCK,其包含如下兩個字段。
kgllkuse字段:“Address of the user session that holds the lock or pin”,主要用于記錄持有lock或pin的用戶地址。
kgllkhdl字段:“Address of the handle for the KGL object”,主要用于記錄handle的 對象地址。
故障發生時,首先查看后臺等待事件,命令及輸出具體如下:
SQL>?select?inst_id,sid,?event,?p1,p1text,p1raw,p2,p2text,p2raw?from?gv$session?where?wait_class<>'Idle';INST_ID??SID?EVENT??????????????P1?P1TEXT???????????????????P1RAW????????? -------?----?------------------?-------------------------???----------------?1???33?library?cache?pin??2081944584?handle?address???000000007C17F408?根據等待事件“library cache pin”獲取“p1 handle address 000000007C17F408”。
關聯視圖“dba_kgllock dk,v$session”獲取鎖信息,命令及輸出如下:
SQL>?select?s.sid,s.sql_id,s.event,dk.*?from?dba_kgllock?dk,v$session?s?where?s.saddr?=?dk.KGLLKUSE?and?KGLLKHDL='000000007C17F408';SID?SQL_ID???????EVENT?????????KGLLKUSE????????????KGLLKHDL?????????KGLLKMOD?KGLLKREQ?KGLL ---?------------?-------------?-------------------?----------------?--------?--------?---- 33??087rrdjwc2act?library?cache?pin?00000000A92FC040?000000007C17F408????????3?????????0??Lock 33??087rrdjwc2act?library?cache?pin?00000000A92FC040?000000007C17F408????????0?????????3??Pin從以上返回結果中可以看出,我們并沒有找到pin的持有者,KGLLKREQ表示當前會話需要申請的鎖模式,KGLLKMOD表示當前系統中持有的鎖模式,由于該系統為RAC,各節點之間的內存結構不同,handle地址不能公用,因此我們需要定位出owner和object_name在其他節點持有pin的會話。命令及輸出如下:
SQL>?select?ADDR,INDX,INST_ID,KGLHDADR,KGLNAOWN,KGLNAOBJ?from?x$kglob?where?KGLHDADR='000000007C17F408';ADDR?????????????INDX?INST_ID?KGLHDADR?????????KGLNAOWN???KGLNAOBJ ----------------?----?-------?----------------?----------?--------- 00007FE9B0B45850?4979???????1?000000007C17F408?SYS????????DUMMY其中,x$kglob為“library cache object”對象的視圖。
RAC節點2根據object_name查找對應的handle地址信息,命令及輸出如下:
SQL>?select?ADDR,INDX,INST_ID,KGLHDADR,KGLNAOWN,KGLNAOBJ?from?x$kglob?where?KGLNAOBJ='DUMMY'ADDR?????????????INDX?INST_ID?KGLHDADR?????????KGLNAOWN??KGLNAOBJ ----------------?----?-------?----------------?---------?--------- 00007F987B1D8ED0?4150???????2?00000000AA193870?SYS???????DUMMY查看鎖的持有情況,命令及輸出如下:
SQL>?select?s.sid,s.sql_id,s.event,dk.*?from?dba_kgllock?dk,v$session?s?where?s.saddr?=?dk.KGLLKUSE?and?KGLLKHDL='00000000AA193870';SID?SQL_ID????????EVENT?????????????KGLLKUSE?????????KGLLKHDL?????????KGLLKMOD?KGLLKREQ?KGLL ---?------------??-----------------?----------------?----------------?--------?--------?---- 424?d4wnj5j8y1mq7?PL/SQL?lock?timer?00000000A9787DA0?00000000AA193870????????1????????0?Lock 424?d4wnj5j8y1mq7?PL/SQL?lock?timer?00000000A9787DA0?00000000AA193870????????2????????0?Pin最終定位節點2上的會話424持有的模式為2(即共享模式)的鎖,堵塞了KGLLKREQ 3排它鎖的申請,為了能夠順利編譯,我們只需要殺掉節點2上的會話424即可。
03 數據誤刪恢復
在筆者多年的工作經歷中,時常會遇到數據被隨意篡改或刪除的情況,那么在沒有備份的情況下又該如何恢復數據呢。
對于drop操作(刪除整個表,包括結構和數據),如果沒有使用purge參數,那么我們可以使用回收站進行恢復,而對于truncate操作(只刪除數據,不刪除表的結構),則需要使用非常規的恢復方法,這些不在本書的討論范圍之內,本節將以delete為例演示數據被誤刪后的恢復。
1. 利用undo閃回查詢
根據undo信息,利用前鏡像,可以把表置于一個刪除前的時間點或SCN(System Change Number),從而找回數據。具體命令如下:
SQL>?select?*?from?emp?as?of?timestamp?to_timestamp('2019-11-05?08:00:00',?'YYYY-MM-DD?HH:MI:SS');但是此方法會受限于undo_retention的配置,默認情況下,undo_retention的值為900秒,即在刪除數據900秒之后,undo中的數據會過期。
但如果業務比較繁忙,在undo表空間不足的情況下,即使鏡像沒有過期,數據也還是會被覆蓋。若此時查詢就會收到“ORA-08180: no snapshot found based on specified time”的報錯信息。
2. logminer挖掘
數據庫所有DML(數據操縱語言)的操作都會記錄在redo日志中,只要歸檔文件還存在,那么所有DML的記錄都可以找回,使用方法如下。
1)確定DML時間點日志信息,命令如下:
SQL>?select?t.THREAD#,?t.SEQUENCE#,?t.NAMEfrom?v$archived_log?twhere?t.FIRST_TIME?>=to_date('2019-11-05?10:24:30',?'yyyy-mm-dd?hh24:mi:ss')and?t.NEXT_TIME?<=to_date('2019-11-05?14:00:30',?'yyyy-mm-dd?hh24:mi:ss');THREAD#??SEQUENCE#?NAME ----------?----------?--------------------------------------------------1??????????2?/app_target/easdb_dg/arch/1_2_1023532682.dbf1??????????1?/app_target/easdb_dg/arch/1_1_1023532682.dbf1??????????3?/app_target/easdb_dg/arch/1_3_1023532682.dbf2)安裝logminer安裝包,默認系統自帶該安裝包,安裝命令如下:
SQL>?@$ORACLE_HOME/rdbms/admin/dbmslm.sql Package?created. Grant?succeeded. Synonym?created.3)添加挖掘日志,添加命令如下:
SQL>?execute?dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_2_1023532682.dbf',options=>dbms_logmnr.new); <!--繼續添加--> SQL>?execute?dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_1_1023532682.dbf',options=>dbms_logmnr.addfile); SQL>?execute?dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_3_1023532682.dbf',options=>dbms_logmnr.addfile);注意:第一個添加日志選項是new,后續添加選項是addfile。
4)開啟logminer,命令如下:
SQL>?execute?dbms_logmnr.start_logmnr(Options?=>?DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);5)查詢v$logmnr_contents視圖獲取挖掘信息,命令如下:
SQL>?select?sql_redo?from?v$logmnr_contents?where?SEG_OWNER='SCOTT'; <!--sql_redo用于記錄當時DML的操作記錄--> SQL>?select?sql_undo?from?v$logmnr_contents?where?SEG_OWNER='SCOTT'; <!--若是誤操作回退,則可以使用sql_undo,執行還原操作-->最終,我們可以根據sql_undo進行DML誤操作恢復。
關于作者:葉樺,Oracle OCM,MySQL認證專家,超10年乙方數據庫維護經驗,美創科技運維服務團隊負責人。具備豐富的行業經驗與技術積累,所服務的對象包括大型運營商、金融機構、政府機關以及制造業等多個行業客戶,對于數據庫技術具有深刻的理解。精通Oracle和MySQL數據庫內核原理、架構規劃和調優診斷,擅長Shell和Python自動化運維開發。
徐浩,美創科技運維部經理,Oracle、MySQL、云數據庫高級認證專家。擁有8年以上的數據庫領域從業經驗,TB級高并發數據庫與中大型項目的管理經驗。對于分布式高可用架構和性能調優有著豐富的實戰經驗,擅長故障診斷及數據災難挽救,服務的行業包括運營商、制造業、金融、醫療、政府等。目前,主要負責Oracle、MySQL、阿里云等技術的研究和運維管理,以及數據庫智能運維平臺的設計開發等工作。
本文摘編自《DBA攻堅指南:左手Oracle,右手MySQL》,經出版方授權發布。
延伸閱讀《DBA攻堅指南》
點擊上圖了解及購買
轉載請聯系微信:DoctorData
推薦語:本書是資深Oracle、MySQL技術專家嘔心瀝血之作,積作者多年的經驗結晶和實踐經驗,也是目前市場上為數不多Oracle和MySQL相結合的數據庫技術書籍。?
劃重點????
干貨直達????
史上最全!用Pandas讀取CSV,看這篇就夠了
既然中臺建設這么重要,為什么還有那么多人反對?
在中國,找個身高1米7年入20萬的老公,到底有多難?
女生學不好數理化,真的嗎?
更多精彩????
在公眾號對話框輸入以下關鍵詞
查看更多優質內容!
PPT?|?讀書?|?書單?|?硬核?|?干貨?|?講明白?|?神操作
大數據?|?云計算?|?數據庫?|?Python?|?爬蟲?|?可視化
AI?|?人工智能?|?機器學習?|?深度學習?|?NLP
5G?|?中臺?|?用戶畫像?|?1024?|?數學?|?算法?|?數字孿生
據統計,99%的大咖都關注了這個公眾號
????
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的3个最常见案例详解DBA日常维护的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 终于有人把SaaS讲明白了
- 下一篇: NumPy入门攻略:手把手带你玩转这款强