前言
前幾天上午在對數據庫的一張表進行操作的時候,由於這張表是按照時間的一張統計表,正好到那天沒有測試數據了,於是我想將表中所有的時間,統一更新到后一個月,於是對80w條數據的更新開始了。整個過程曲折的一批。同時學到了很多知識,在此進行記錄。希望對大家有幫助。
首先是大批量更新,由於數據已經進行了分區,開始對分區進行分析,然后大批量操作死鎖,對死鎖的解決,最后存儲過程來解決數據的大批量插入。
曲折的過程開始
由於測試數據到21號就沒了,21號一上去,發現開發的功能,都沒有數據了,圖表也都空了。查詢原因發現測試數據沒了。於是打算開始造數據。此時數據庫已經有80多w的數據,當時想着將所有數據的collect_time時間字段向后推遲一個月,即可。當時也沒想優化問題。於是寫下sql。
update i_people_collect set collect_time = collect_time+30
此sql將表的所有時間向后推遲一個月。於是開始執行。
此時報錯:ORA-14402: 更新分區關鍵字列將導致分區的更改。
於是發現此表的collect_time列進行了分區處理。
我們可以先開啟表的行移動來允許對分區字段的update 操作。sql如下
alter table xxx enable row movement;
之后再執行update發現可以執行,執行完畢后,記得關閉行移動。
alter table xxx disable row movement;
回到剛才我們執行update語句,預計會慢,但是發現執行了20分鍾還沒有結束。於是懷疑報錯了。就強行終止。但是此時終止也不好使了。。大概是占用資源太多,不好釋放。
於是強行關掉pl/sql。重新登錄。這里我們先分析一下,執行update操作為什么會這么慢。
分區表某一行更新時,如果更新的是分區列,並且更新后的列值不屬於原來的這個分區,如果開啟了這個選項,就會把這行從這個分區中delete掉,並加到更 新后所屬的分區。相當於一個隱式的delete+insert,但是不會觸發insert/delete觸發器。如果沒有開啟這個選項,就會在更新時報錯 ORA-14402;
這一操作產生影響的特殊之處在於這是個DML操作,是和online transaction密切相關。對於這樣一個UPDATE,實際上分為3步:先從原有分區將數據刪除;將原數據轉移到新分區上;更新數據。
其影響就在於以下幾個方面:
一 個UPDATE被分解為DELET、INSERT、UPDATE三個操作,增加了性能負擔。其中,DELETE的查詢條件與原UPDATE的查詢條件相 同,新的UPDATE的查詢條件是基於INSERT生成的新的ROWID,相應的Redo Log、Undo Log會增加;
如果Update語句還涉及到了Local Index的字段的話,新、舊2個分區上的Local Index都要被更新。
由於我們更新的是collect_time列。collect_time列又正好是分區列。於是就產生了上面的這種情況。造成執行速度十分的緩慢。
原因分析完畢。繼續說接下來發生的問題。
重新連接到PL/Sql后,對剛才的表進行查詢,發現一直執行sql,並不返回結果。於是考慮剛才的sql還在執行的問題。
通過pl/sql的工具,會話,發現剛才的會話仍然存在,沒有斷開連接。這就坑爹了啊。通過會話來對連接強制結束。發現還是不能操作剛才的表。於是考慮了一下,可能是表發生了死鎖。
於是執行查詢哪些表產生了死鎖的sql,如下
select b.owner,b.object_name,a.session_id,a.locked_mode from v$locked_object a,dba_objects b where b.object_id = a.object_id;
通過結果發現,剛才的表果然已經被鎖定了。
繼續向下看是哪個用戶的哪個進程造成的死鎖
--查看那個用戶那個進程照成死鎖 select b.username,b.sid,b.serial#,logon_time from v$locked_object a,v$session b where a.session_id = b.sid order by b.logon_time;
--查看連接的進程 SELECT sid, serial#, username, osuser FROM v$session;
--查出鎖定表的sid, serial#,os_user_name, machine_name, terminal,鎖的type,mode SELECT s.sid, s.serial#, s.username, s.schemaname, s.osuser, s.process, s.machine, s.terminal, s.logon_time, l.type FROM v$session s, v$lock l WHERE s.sid = l.sid AND s.username IS NOT NULL ORDER BY sid;
此時通過這些查詢,我們已經能夠定位是哪個進程導致了鎖表的產生。同時獲取到了進程的sid以及serial。
執行中斷進程的sql,
alter system kill session'210,11562';
講道理,此時已經進行了進程的結束,但是發現表還是在鎖着的。於是可能是查看一下造成死鎖的這一進程的狀態。
select saddr, sid, serial#, paddr, username, status, machine from v$session where username is not null
通過status發現鎖定的進程的狀態已經改變為KILLED,這種狀態可能導致長時間的未釋放資源,PMON並沒有對其進行清除,等了很久仍然是鎖表狀態。
於是可能需要操作系統級別的對進程進行清除。
我們查詢出會話進程在操作系統中的進程id。
select a.spid,b.sid,b.serial#,b.username,b.status from v$process a,v$session b where a.addr=b.paddr ;
我們進入linux后台。通過kill -9 spid,此時執行后,發現表已經解鎖了,死鎖結束。呼~不容易。
接下來問題又來了,我們如何繼續更新數據呢。最終決定實用存儲過程來進行增加數據。
create or replace procedure aaa(startdate in date, days in number) as --生成的數據包含startdate當天 i number; begin i := 0; while i < days loop insert into aaa1 select sec_pkid.nextval,startdate + i, '字段名','字段名','字段名','字段名' from aaa2 t where collect_time = to_date('2018-11-09','yyyy-mm-dd'); i := i+1; commit; end loop; end aaa;
