1 場景介紹
一個生產系統的數據庫,Oracle單機遷移到Oracle RAC數據庫,很快數據庫里就出現了大量的行鎖事件,連接數據庫的十幾臺weblogic應用服務器報了大量的連接超時錯誤,應用也丟失了大量工單信息。
原因是什么呢?
獲取了遷移后數據庫的AWR報告,和遷移之前單機部署時的AWR報告對比一下,就發現遷移后數據庫的行鎖不但數量多了,等待的時間也增加了十幾倍,最長的行鎖等待時間達到30分鐘以上。生產環境上的環境的數據不方便發到網上,本文通過常見的HR場景模擬并分析這種情況。
2 從AWR報告分析
數據庫性能問題首先要分析的是AWR報告,報告的獲取辦法這里就不寫了。分析行鎖性能之前先看一下數據庫的整體性能

DB time是報告采樣時間的接近2倍,再看這臺數據庫服務器的配置1cpu,1核心,可以看到數據庫可能會有性能問題。
下面看一下數據庫的負載情況

數據庫的DB time值很大, 超過了服務器的cpu數,DB CPU值接近0,DB time主要應該消耗在等待時間上,在從每事務執行的sql來看,數據庫里的事務應該比較復雜,每個事務執行的語句達到69.3條,結合每個事務的邏輯讀,redo size和block change來看,這應該是一個復雜事務型的數據庫系統。
根據上面的分析,數據庫可能有等待事件方面的問題,下面就需要查看前臺等待事件,接著檢查AWR報告中Top 10 Foreground Events by Total Wait Time。

行鎖等待事件enq: TX - contention位于第一行,采樣事件內,等待了1208次,總等待時間3589秒,這個事件的平均等待時間是2971.04ms,事件的總等待事件占DB time 96.1%,是DB time的主要消耗者。這種情況影響的不是數據庫的整體性能,而是相關應用的性能。
前臺等待事件更詳細的信息位于Wait Events Statistics之下,這一部分可以看到等待事件的總體性信息,比如發生次數,平均等待時長,占用的數據庫時間比例等,內容如下

行鎖等待事件enq: TX - contention位于第一行,是總體等待時間處于第二位的等待事件,總體等待時間才1秒,占用的數據庫時間比例5.57%,從這兩點看來,這個事件對數據庫的性能影響不大。事件的平均等待時間是21.10毫秒,每個事務發生1.09個等待事件,也就是說,這個等待事件的影響是普遍的,差不多每個事務都要等待這個等待事件,這個影響的范圍是相當的廣了,不能忽視。
分析到這里,對這個事件的影響已經有一個總體性的,大概的把握了。但是,對解決問題來說還不夠,我們還需要知道是哪些應用產生了這個行鎖,這些應用操作了哪些數據,做了什么樣的操作?
產生行鎖的表可以在數據庫的AWR報告里查到,位于Segment Statistics的Segments by Row Lock Waits部分

這部分的內容如下

對test用戶的兩個表是產生行鎖的根源,它們產生幾乎一樣多的行鎖。要知道是哪個應用,哪條SQL語句產生的行鎖,就需要查詢數據庫的ash報告了。
3 從ash報告分析
3.1 事件及其參數
ASH報告里可以看到采用時間段的top事件和top事件的參數值,top user事件如下

事件的參數值如下

通過行鎖事件的第2個和第三個參數可以推算出是哪個事務導致了這個事件。
3.2 事件的相關SQL
在top SQL部分可以看到事件相關的SQL

點擊SQL_ID可以看到完整的sql語句。如果SQL語句沒有采用訪問變量,通過sql語句就可以得出語句操作的是哪個表的哪部分數據,可以反饋給開發分析了。
3.3 事件的相關會話

這里顯示的事件的會話信息,通過里面的program信息可以大致看出導致行鎖的應用是哪一類的程序。這里面的會話id里面有實例信息,就是在括弧里面的數字。這個信息在Oracle rac環境里是十分有用的,如果事件的會話分布到不同的實例上,基本上可以認為是RAC環境惡化了等待事件的影響。
3.5 事件發生的時間

這部分信息也很有用,從這里可以看出哪個時間段發生的最頻繁,和業務返回的問題時間段是不是重合,表里slot列是之間間隔內的采樣次數,event count是這個時間段內這個時間發生的次數,以第一行為例,這個時間段內共采用了1262次,時間發生了1228次,幾乎每個采樣都發生了這個事件,行鎖發生是十分頻繁的。
4 用數據庫腳本分析
上面通過awr和ash報告分析,我們得到了產生行鎖的sql語句,行鎖發生的最頻繁的事件段,如果語句執行時也會知道是操作哪些數據時發生的行鎖,如果時rac環境還會知道是阻塞會話,非阻塞會話在各個實例上分布。這些信息雖然很多,但是要采取措施還似乎缺少一些必要的信息。比如下面幾個方面:
- 如果語句執行時使用了訪問變量,我們又如何得知發生行鎖時訪問的數據。
- 如果是rac環境,我們怎么知道阻塞會話及被其阻塞的會話是在同一個實例上還是在不同的實例上。
- 如果行鎖影響了應用的響應時間,我們需要知道行鎖最長的等待時間是多少,會導致應用連接數據庫超時嗎?
上面這些信息,awr報告和ash報告都沒有提供,需要在數據庫內使用SQL來查詢,近期的歷史會話信息在V$ACTIVE_SESSION_HISTORY視圖里查詢,時間過去較長了,就需要在DBA_HIST_ACTIVE_SESS_HISTORY視圖里查詢了。
4.1 查詢和分析時間的等待時間
SQL> l
1 select EVENT,max(WAIT_TIME),max(TIME_WAITED) from DBA_HIST_ACTIVE_SESS_HISTORY where
2 SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss')
3 and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
4* group by event
SQL> /
EVENT MAX(WAIT_TIME) MAX(TIME_WAITED)
---------------------------------------------------------------- --------------- ----------------
enq: TX - row lock contention 0 21509678
buffer busy waits 0 218143
17 0
WAIT_TIME是指會話上一次的等待時間的等待時間,如果采用時會話正在等待這個值是0,TIME_WAITED是會話已在這個等待事件上等待的時間。這兩個時間的單位都是微妙。這兩個值都有意義,哪個值過大都是事件的等待時間過長。從上面語句的輸出可以看到,行鎖等待事件最長時間長達21秒多,對數據庫來說,這個時間已經很長了,通常會對應用的性能造成很大的影響,大概率會導致數據庫連接超時。
下面這條sql語句以秒為單位,統計時間發生次數在時間上的分布
SQL> select EVENT, trunc((WAIT_TIME+TIME_WAITED)/1000000) WAIT_TIME_s ,count(*),count(distinct SESSION_ID) from DBA_HIST_ACTIVE_SESS_HISTORY
where SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
group by EVENT, trunc((WAIT_TIME+TIME_WAITED)/1000000); 2 3
EVENT WAIT_TIME_S COUNT(*) COUNT(DISTINCTSESSION_ID)
---------------------------------------------------------------- ----------- ---------- -------------------------
enq: TX - row lock contention 0 584 66
enq: TX - row lock contention 17 2 1
enq: TX - row lock contention 3 74 35
enq: TX - row lock contention 21 52 26
enq: TX - row lock contention 2 10 5
buffer busy waits 0 10 5
enq: TX - row lock contention 4 12 6
enq: TX - row lock contention 1 16 8
從上面輸出可以看到,21秒以上等待時間采樣到52次,有26個不同的會話,這個等待事件的影響范圍也很很大。有了上面的事件信息,我們可以查詢一下這些等待時間較長的會話及其阻塞會話在各個實例間的分布
4.2 行鎖事件相關會話在實例間的分布分析
SQL> select EVENT, INSTANCE_NUMBER,SESSION_ID,BLOCKING_INST_ID,BLOCKING_SESSION,TIME_WAITED from DBA_HIST_ACTIVE_SESS_HISTORY
where SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
and TIME_WAITED>=20000000; 2 3
EVENT INSTANCE_NUMBER SESSION_ID BLOCKING_INST_ID BLOCKING_SESSION TIME_WAITED
---------------------------------------------------------------- --------------- ---------- ---------------- ---------------- -----------
enq: TX - row lock contention 1 140 1 120 21508641
enq: TX - row lock contention 1 144 1 120 21508771
enq: TX - row lock contention 1 146 1 120 21509384
enq: TX - row lock contention 1 150 1 120 21013971
enq: TX - row lock contention 1 151 1 120 21508560
enq: TX - row lock contention 1 153 1 120 21508475
enq: TX - row lock contention 1 154 1 120 21508419
enq: TX - row lock contention 1 81 1 120 21499062
enq: TX - row lock contention 1 82 1 120 21487559
enq: TX - row lock contention 1 85 1 120 21486437
enq: TX - row lock contention 1 86 1 120 21505717
EVENT INSTANCE_NUMBER SESSION_ID BLOCKING_INST_ID BLOCKING_SESSION TIME_WAITED
---------------------------------------------------------------- --------------- ---------- ---------------- ---------------- -----------
enq: TX - row lock contention 1 99 1 120 21489908
enq: TX - row lock contention 1 101 1 120 21492926
enq: TX - row lock contention 1 103 1 120 21502255
enq: TX - row lock contention 1 105 1 120 21509236
enq: TX - row lock contention 1 107 1 120 21507457
enq: TX - row lock contention 1 108 1 120 21491744
enq: TX - row lock contention 1 112 1 120 21488139
enq: TX - row lock contention 1 118 1 120 21490533
enq: TX - row lock contention 1 119 1 120 21508476
enq: TX - row lock contention 1 122 1 120 21495585
enq: TX - row lock contention 1 126 1 120 21509678
EVENT INSTANCE_NUMBER SESSION_ID BLOCKING_INST_ID BLOCKING_SESSION TIME_WAITED
---------------------------------------------------------------- --------------- ---------- ---------------- ---------------- -----------
enq: TX - row lock contention 1 131 1 120 21509507
enq: TX - row lock contention 1 132 1 120 21508960
enq: TX - row lock contention 1 138 1 120 21509091
enq: TX - row lock contention 1 139 1 120 21509608
enq: TX - row lock contention 1 81 1 120 21499062
......--省略多行
enq: TX - row lock contention 1 151 1 120 21508560
enq: TX - row lock contention 1 153 1 120 21508475
enq: TX - row lock contention 1 154 1 120 21508419
52 rows selected.
這里是單機環境,我遇到的是生產上RAC環境,INSTANCE_NUMBER 和BLOCKING_INST_ID 的值不同。這里可以看出,阻塞會話是同一個,應該是同一個應用導致的阻塞,其執行的SQL也可能是同一個,或者是數據維護時調整應用正在訪問的數據所致。下面我們進一步查詢一下是執行是執行哪個SQL導致的這個行鎖。
4.3 行鎖相關的SQL語句
SQL> select distinct EVENT,dbms_lob.SUBSTR(SQL_TEXT,200) sql_text from DBA_HIST_ACTIVE_SESS_HISTORY s left join DBA_HIST_SQLTEXT t on t.SQL_ID=s.SQL_ID
where s.SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
and s.TIME_WAITED>=20000000; 2 3
EVENT
----------------------------------------------------------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
enq: TX - row lock contention
UPDATE EMPLOYEES SET DEPT_ID = (SELECT DEPT_ID FROM DEPARTMENTS WHERE DEPT_NAME = :B2 ) WHERE DEPT_ID = (SELECT DEPT_ID FROM DEPARTMENTS WHERE DEPT_NAME = :B3 ) AND ROWNUM <= :B1
enq: TX - row lock contention
UPDATE EMPLOYEES SET SALARY = SALARY * (1 + :B2 ), STATUS = CASE WHEN SALARY * (1 + :B2 ) > 15000 THEN '高薪' ELSE STATUS END WHERE EMP_ID = :B1
這個事件等待事件超過20秒的會話執行了兩條SQL語句。
在看一下阻塞會話的,從前一節的輸出來看,阻塞會話的id是120,使用下面的語句來查詢
SQL> select distinct EVENT,dbms_lob.SUBSTR(SQL_TEXT,200) sql_text from DBA_HIST_ACTIVE_SESS_HISTORY s left join DBA_HIST_SQLTEXT t on t.SQL_ID=s.SQL_ID
where s.SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
and s.SESSION_ID=120; 2 3
EVENT
----------------------------------------------------------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
enq: TX - row lock contention
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 1.2 WHERE DEPT_ID = :B1
阻塞會話執行的語句只有一條。
到這里,等待事件最長的等待事件的起因已經完全清楚了,可以返回給應用調整程序了。
擴大一下范圍,分析一下這個時間段內所有的會話及其阻塞會話執行的SQL語句,使用下面的SQL
SQL> ;
1 select distinct a.EVENT,dbms_lob.SUBSTR(ta.SQL_TEXT,200) sql_text,dbms_lob.SUBSTR(tb.SQL_TEXT,200) b_sql_text from DBA_HIST_ACTIVE_SESS_HISTORY a inner join DBA_HIST_ACTIVE_SESS_HISTORY b on a.BLOCKING_SESSION=b.SESSION_ID and a.SAMPLE_TIME=b.SAMPLE_TIME
2 left join DBA_HIST_SQLTEXT ta on ta.SQL_ID=a.SQL_ID
3 left join DBA_HIST_SQLTEXT tb on tb.SQL_ID=b.SQL_ID
4* where a.SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
EVENT
----------------------------------------------------------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
B_SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
enq: TX - row lock contention
UPDATE EMPLOYEES SET DEPT_ID = (SELECT DEPT_ID FROM DEPARTMENTS WHERE DEPT_NAME = :B2 ) WHERE DEPT_ID = (SELECT DEPT_ID FROM DEPARTMENTS WHERE DEPT_NAME = :B3 ) AND ROWNUM <= :B1
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 0.9, MANAGER_ID = NULL WHERE DEPT_ID = :B1
enq: TX - row lock contention
SELECT E.EMP_ID, E.EMP_NAME, E.SALARY, D.DEPT_NAME FROM EMPLOYEES E, DEPARTMENTS D WHERE E.DEPT_ID = D.DEPT_ID AND D.DEPT_NAME IN ('技術部', '銷售部') AND ROWNUM <= 30 FOR UPDATE
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 0.9, MANAGER_ID = NULL WHERE DEPT_ID = :B1
enq: TX - row lock contention
SELECT E.EMP_ID, E.EMP_NAME, E.SALARY, D.DEPT_NAME FROM EMPLOYEES E, DEPARTMENTS D WHERE E.DEPT_ID = D.DEPT_ID AND D.DEPT_NAME IN ('技術部', '銷售部') AND ROWNUM <= 30 FOR UPDATE
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 1.2 WHERE DEPT_ID = :B1
enq: TX - row lock contention
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 1.2 WHERE DEPT_ID = :B1
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 1.2 WHERE DEPT_ID = :B1
enq: TX - row lock contention
UPDATE EMPLOYEES SET SALARY = SALARY * (1 + :B2 ), STATUS = CASE WHEN SALARY * (1 + :B2 ) > 15000 THEN '高薪' ELSE STATUS END WHERE EMP_ID = :B1
UPDATE DEPARTMENTS SET BUDGET = BUDGET * 1.2 WHERE DEPT_ID = :B1
enq: TX - row lock contention
UPDATE EMPLOYEES SET SALARY = SALARY * (1 + :B2 ), STATUS = CASE WHEN SALARY * (1 + :B2 ) > 15000 THEN '高薪' ELSE STATUS END WHERE EMP_ID = :B1
UPDATE EMPLOYEES SET SALARY = SALARY * (1 + :B2 ), STATUS = CASE WHEN SALARY * (1 + :B2 ) > 15000 THEN '高薪' ELSE STATUS END WHERE EMP_ID = :B1
這個SQL阻塞鏈也可以發給開發全面解決一下行鎖問題。
5 哪些數據,哪個用戶導致行鎖
在運維的過程中,也有些開發需要知道是訪問哪些數據導致了行鎖,這在多個業務系統共享一套數據時經??梢杂龅剑€是以上面的超過20秒的等待時間的行鎖為例
SQL> select EVENT,to_char(SAMPLE_TIME,'YYYY-MM-DD hh24:mi:ss') SAMPLE_TIME , CURRENT_OBJ#,CURRENT_FILE#, CURRENT_BLOCK#,CURRENT_ROW# from DBA_HIST_ACTIVE_SESS_HISTORY
where SAMPLE_TIME between to_date('2025-12-17 11:56:00', 'YYYY-MM-DD hh24:mi:ss') and to_date('2025-12-17 12:02:00','YYYY-MM-DD hh24:mi:ss')
2 3 and TIME_WAITED>=20000000;
EVENT SAMPLE_TIME CURRENT_OBJ# CURRENT_FILE# CURRENT_BLOCK# CURRENT_ROW#
---------------------------------------------------------------- ------------------- ------------ ------------- -------------- ------------
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
EVENT SAMPLE_TIME CURRENT_OBJ# CURRENT_FILE# CURRENT_BLOCK# CURRENT_ROW#
---------------------------------------------------------------- ------------------- ------------ ------------- -------------- ------------
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
EVENT SAMPLE_TIME CURRENT_OBJ# CURRENT_FILE# CURRENT_BLOCK# CURRENT_ROW#
---------------------------------------------------------------- ------------------- ------------ ------------- -------------- ------------
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
EVENT SAMPLE_TIME CURRENT_OBJ# CURRENT_FILE# CURRENT_BLOCK# CURRENT_ROW#
---------------------------------------------------------------- ------------------- ------------ ------------- -------------- ------------
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
EVENT SAMPLE_TIME CURRENT_OBJ# CURRENT_FILE# CURRENT_BLOCK# CURRENT_ROW#
---------------------------------------------------------------- ------------------- ------------ ------------- -------------- ------------
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
enq: TX - row lock contention 2025-12-17 11:58:10 72505 1024 1443 78
52 rows selected.
可以看到這個行鎖是在訪問同一條數據時遇到的,怎樣查詢是哪條數據呢,可以dump這個數據塊來看,這樣比較麻煩,查起來也費勁??梢酝ㄟ^找到這行數據的rowid找到這條數據
--先找到這條數據的所在的表
SQL> select OWNER,OBJECT_NAME,OBJECT_ID,DATA_OBJECT_ID,OBJECT_TYPE from dba_objects where DATA_OBJECT_ID=72505;
OWNER OBJECT_NAME OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
-------------------------------- -------------------------------- ---------- -------------- -----------------------
TEST EMPLOYEES 72505 72505 TABLE
--查詢這條數據,條件是這條數據的rowid,及blockid
SQL> select EMP_ID,
rowid,
dbms_rowid.rowid_object(rowid) "object",
2 3 4 dbms_rowid.rowid_relative_fno(rowid) "file",
dbms_rowid.rowid_block_number(rowid) "block",
dbms_rowid.rowid_row_number(rowid) "row"
from TEST.EMPLOYEES where dbms_rowid.rowid_row_number(rowid)=78; 5 6 7
EMP_ID ROWID object file block row
---------- ------------------ ---------- ---------- ---------- ----------
79 AAARs5AAAAAAAWjABO 72505 0 1443 78
這里文件號對不上,查詢一下數據庫的數據文件信息,如下
SQL> l
1* select FILE_NAME,FILE_ID,RELATIVE_FNO from dba_data_files
SQL> /
FILE_NAME FILE_ID RELATIVE_FNO
---------------------------------------------------------------- ---------- ------------
/opt/oracle/oradata/FREE/FREEPDB1/sysaux01.dbf 13 1024
/opt/oracle/oradata/FREE/FREEPDB1/users01.dbf 15 1024
/opt/oracle/oradata/FREE/FREEPDB1/system01.dbf 12 1024
/opt/oracle/oradata/FREE/FREEPDB1/undotbs01.dbf 14 1024
這個是容器數據庫,所有數據文件的RELATIVE_FNO都是1024,檢查一下這幾個表空間,都是bigfile表空間,RELATIVE_FNO這個值沒什么意義了,上面查到的那條數據應是對的。
6 行鎖的解決辦法
分析的結果反饋到開發,生產環境是rac,等待行鎖會話和它的阻塞會話大多不在一個實例上。在應用無法調整的情況下,調整了前端應用服務器的配置,將其數據庫連接設置為主備配置,所有的應用服務器都連接到rac中的一個實例上。
調整后,行鎖等待事件得到了很大的緩解,行鎖的數量少了,行鎖的等待時間也縮短了很多,應用服務器也不再發生連接超時的錯誤了。
7 行鎖的原理及常見場景
當一條sql語句更新或刪除一行數據時,事務只獲得這一行的鎖,獲得的這個鎖就是行鎖。鎖這一行的是某一個事務,而不是會話。oracle的規則是這樣的,寫一行數據庫數據時加行鎖,讀一行數據不對行枷鎖,寫阻塞寫,寫不阻塞讀,讀不阻塞讀和寫。
當多個事務同時更新(包括刪除) 統一數據時(表或索引),就會發生行鎖競爭,在Oracle等待事件里就是enq: TX - row lock contention enq,enq時隊列的意思,在oracle數據庫里同鎖是一個意思。
發生行鎖競爭一個最常見的場合時多個會話同時更新或刪除同一表的同一條數據,或者是會話更新一行數據的同時其它會話正在刪除數據,反過來也一樣。
表上有唯一索引時,插入數據有時也會導致行鎖,這主要時由于插入數據時唯一索引的值引起的沖突,產生唯一索引的值的方法不正確,是表設計時唯一索引的選擇不正確的結果。
產生行鎖競爭的另一個常見的場景是位圖索引,位圖索引的一個值對應表中的多個值,雖然我們在表中更新的是不同的值,在位圖索引中對應的是一個值,這樣即使我們更新的表中的不同的值也會導致行鎖競爭。這也是位圖索引在oltp事務中被禁止使用的原因。
8 注意事項
等待事件的歷史直方圖統計信息在AWR報告里可以查到,我這個環境是Oracle 26 AI free版數據庫,沒有這部分信息。企業版的19C里面有這一部分如圖

在Wait Events Statistics部分里有Wait Event Histogram,依據等待時間的不同分成了6個部分。下面是等待時間最長的那bufen

這部分是等待事件在時間上的分布的統計 信息,以第一行buffer busy waits為例,可以看到等待時間從4分鐘到1小時的有11個。在后面的% of Total Waits部分里可以看到100%的等待事件在2分鐘之內,這個值應該是近似值,誤差在0.5%以內,這部分的注釋說了,.0意味著小于0.5%,空值才是真正的0。這一部分的顯示似乎有些矛盾,前面說的4分鐘到1小時的有11個,后面的百分比在相應的等待事件段內都是空值,也就是真正的0。這和awr報告統計的方法有關,具體哪個更準確,這就需要我們去數據庫里去查詢會話歷史信息進行檢驗核實。
這一部分對我們全面分析等待事件的影響是十分有幫助的。




