2014-01-20 115 views
1

我在Oracle SQL中遇到以下問題。 gt_general2是一個全局臨時表。使用INSERT INTO的Oracle SQL鎖定行

INSERT INTO gt_general2 (n_1, n_2, n_3) 
select p.x_id, a.y_id, a.dollar_amt 
from table_P p, table_A a 
where p.x_id = a.x_id 
    and a.h_date = i_date 
    and a.dollar_amt > 0 
for update of p.x_id, a.dollar_amt nowait; --this is the problem statement 

當我嘗試使用SELECT ... FOR語法的INSERT ... SELECT語法時,Oracle不喜歡。 我想在執行此插入操作時鎖定行,因爲真正的查詢實際上相當複雜(並且會對其他視圖進行一些檢查以確保父記錄存在,等等),並且這樣我可以在執行此操作時插入臨時表該行鎖定在一次通過中。

是否有一些其他語法我錯過,這將允許我這樣做?在概念層面上,我沒有看到Oracle爲什麼不允許我從表P和A中選擇行,將它們鎖定在過程中,並將值插入到另一個表Z中的任何原因。我已經可以完成此操作:

select p.x_id, a.y_id 
BULK COLLECT into x_id_list, y_id_list 
from table_P, table_A 
where ... 
for update nowait; 

當然可以通過執行此查詢兩次鎖定行 - 一次鎖定,一次插入。以前,我是這樣做的,並使用鎖定查詢檢索數組中的「x_id」值以幫助執行INSERT INTO查詢。但我碰到的問題是,x_id值不足以識別我想要的行;我需要這對(x_id,y_id),並且不能創建一個新的嵌套表類型來存儲它。

+0

「無法創建一個新的嵌套表類型來存儲它」......由於技術限制或特權? –

回答

2

這真的取決於你爲什麼要這樣做。

如果您需要訪問行中的值作爲事務的開始而不是每個查詢的開始,而不管它們是否更改,那麼我會考慮將會話更改爲將讀取一致性模式更改爲「可序列化」,而不是默認的「讀取已提交」。

如果你真的要回去更新這些行,那麼我會考慮使用公共表表達式(CTE)而不是全局臨時表來將所有邏輯封裝在單個查詢中。對於很多情況,GTT和CTE的數量相當於相同的優化,當然,您無法在整個流程中對CTE進行部分分析,也無法針對它創建索引。

+0

這是第二種情況 - 該過程是:1)通過查詢找到核心表中的行並鎖定它們以進行更新,2)將從這些行派生的一堆值插入臨時表中,3)執行大量通過遊標在臨時表中的美元金額之間的重新分配計算,4)使用臨時表來存儲用於子記錄訪問的父序列NEXTVAL值,並插入到單獨的事務日誌表中,5)返回並更新核心具有新值的表格。我嘗試使用WITH()表,但是這個過程對於他們來說太複雜了。 – ubanerjea

3

您不能在單個語句中合併INSERTSELECT FOR UPDATE。但是,您可以使用常規的SELECT FOR UPDATE,並使用批量插入批量收集來實現近乎相似的性能。

如果您在PL/SQL代碼中聲明集合,則不需要創建新的永久SQL類型對象。

下面是一個例子,首先,設置:

SQL> CREATE TABLE table_a (x_id NUMBER, y_id NUMBER, dollar_amt NUMBER); 
Table created 
SQL> CREATE TABLE table_p (x_id NUMBER); 
Table created 
SQL> INSERT INTO table_a VALUES (1, 1, 0); 
1 row inserted 
SQL> INSERT INTO table_a VALUES (2, 1, 10); 
1 row inserted 
SQL> INSERT INTO table_a VALUES (3, 1, 20); 
1 row inserted 
SQL> INSERT INTO table_p VALUES (2); 
1 row inserted 
SQL> INSERT INTO table_p VALUES (3); 
1 row inserted 
SQL> INSERT INTO table_p VALUES (4); 
1 row inserted 
SQL> CREATE GLOBAL TEMPORARY TABLE gt_general2(
    2  n_1 NUMBER, 
    3  n_2 NUMBER, 
    4  n_3 NUMBER); 
Table created 

鎖定和插入過程(上11.2.0.2.0測試):

SQL> DECLARE 
    2  CURSOR cc IS 
    3  SELECT p.x_id, a.y_id, a.dollar_amt 
    4   FROM table_P p, table_A a 
    5   WHERE p.x_id = a.x_id 
    6   --AND a.h_date = i_date 
    7   AND a.dollar_amt > 0 
    8   FOR UPDATE OF p.x_id, a.dollar_amt NOWAIT; 
    9  TYPE table_rec IS TABLE OF cc%ROWTYPE; 
10  l_table table_rec; 
11 BEGIN 
12  OPEN cc; 
13  LOOP 
14  -- bulk fetch 
15  FETCH cc BULK COLLECT INTO l_table LIMIT 100; 
16  EXIT WHEN l_table.count=0; 
17  -- bulk insert 
18  FORALL i IN 1..l_table.count 
19   INSERT INTO gt_general2 (n_1, n_2, n_3) 
20    VALUES (l_table(i).x_id, 
21      l_table(i).y_id, 
22      l_table(i).dollar_amt); 
23  END LOOP; 
24  CLOSE cc; 
25 END; 
26/
PL/SQL procedure successfully completed 

這將做散裝選擇分批/插入一行100行。這應該比後面插入語句的鎖定語句更快。另外,你可以保證所插入的行與鎖定的行完全相同(然而如果表已被修改,則兩個執行的語句可能會返回不一致的結果)。

+0

嘿,謝謝你的建議。不過,我遇到了這個問題。它編譯得很好,但運行時出現了崩潰:_Error從行開始:7在命令中 - 錯誤報告 - 沒有更多的數據要從socket讀取._這意味着這導致某種未處理的異常,我猜測,因爲我有我用這個概念創建的小測試程序中的異常塊。 – ubanerjea

+0

始終得到錯誤。這是由於將遊標批量收集到NT中的代碼引起的,因爲在將所有後續位註釋掉之後,該部分仍然導致錯誤。實際上即使沒有批量收集,這仍然失敗:x_count:= 0; x_lock_updts.EXTEND; - 鎖定行更新; open cx_lock; 循環 x_count:= x_count + 1; 將cx_lock提取到x_lock_updts(x_count); cx_lock%NOTFOUND時退出; x_lock_updts.EXTEND; end loop; close cx_lock; – ubanerjea

+1

您的設置還有其他問題,我列出了一個完整的示例。你使用什麼版本?如果您使用的是10g或更舊的版本,則可能需要將記錄表拆分爲3個嵌套表格,並將其批量提取到它們中。 –