2013-02-27 84 views
5

我有一個使用Devart和Entity Framework訪問的Oracle數據庫。在數據庫表中並行讀取和更新

有一張名爲IMPORTJOBS的表格,列STATUS

我也有多個進程同時運行。他們分別讀取IMPORTJOBS中的第一行,其狀態爲'REGISTERED',將其置於狀態'EXECUTING',如果已完成,則將其置於狀態'EXECUTED'

現在,因爲這些進程並行運行,我認爲以下可能發生:

  • 過程A讀取其中有狀態REGISTERED排10,
  • 進程B也讀其仍具有狀態REGISTERED排10 ,
  • 進程A將第10行更新爲狀態EXECUTING

進程B不應該能夠讀取第10行,因爲進程A已經讀取並且將要更新其狀態。

我應該如何解決這個問題?把事務讀取和更新?還是應該使用一些版本控制方法或其他方法?

謝謝!

編輯:感謝接受的答案我得到它的工作和記錄在這裏:http://ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database

回答

2

您應該使用數據庫的內置鎖定機制。不要重新發明輪子,尤其是因爲RDBMS是設計的來處理併發性和一致性。

在Oracle 11g中,我建議您使用SKIP LOCKED功能。例如,每個處理可以調用​​這樣的功能(假設id是號碼):

CREATE OR REPLACE TYPE tab_number IS TABLE OF NUMBER; 

CREATE OR REPLACE FUNCTION reserve_jobs RETURN tab_number IS 
    CURSOR c IS 
     SELECT id FROM IMPORTJOBS WHERE STATUS = 'REGISTERED' 
     FOR UPDATE SKIP LOCKED; 
    l_result tab_number := tab_number(); 
    l_id number; 
BEGIN 
    OPEN c; 
    FOR i IN 1..10 LOOP 
     FETCH c INTO l_id; 
     EXIT WHEN c%NOTFOUND; 
     l_result.extend; 
     l_result(l_result.size) := l_id; 
    END LOOP; 
    CLOSE c; 
    RETURN l_result; 
END; 

這將返回10行(如果可能的話)未鎖定。這些行將被鎖定,會話不會彼此阻塞。

在10g和之前,因爲Oracle返回一致的結果,明智地使用FOR UPDATE,你不應該有你描述的問題。例如考慮以下SELECT

SELECT * 
    FROM IMPORTJOBS 
WHERE STATUS = 'REGISTERED' 
    AND rownum <= 10 
FOR UPDATE; 

如果所有進程保留此選擇自己的行會發生什麼?這將如何影響您的場景:

  1. 會話A獲得10行未處理。
  2. 會話B將獲得相同的10行,被阻止並等待會話A.
  3. 會話A更新所選行的狀態並提交其事務。
  4. 由於數據已被修改,並且我們指定了FOR UPDATE(此子句強制Oracle獲取該塊的最後一個版本),因此Oracle現在會(自動)重新運行會話B的選擇。
    這意味着會話B將獲得10 新行

因此在這種情況下,您沒有一致性問題。另外,假設事務請求一行並更改其狀態的速度很快,併發性影響將很小。

+0

謝謝,我試圖執行「SELECT * FROM IMPORTJOBS WHERE STATUSCODE ='REGISTERED'AND ROWNUM <= 1 FOR UPDATE SKIP LOCKED」,但它仍然返回來自不同進程的同一行? – 2013-02-28 08:40:58

+1

(1)確保您已關閉自動提交功能:無法在沒有事務的情況下鎖定某一行。 (2)'FOR UPDATE SKIP LOCKED'和'rownum' [不會像你期望的那樣工作](http://stackoverflow.com/questions/5847228/oracle-select-for-update-behaviour) - 這是因爲在WHERE子句之後,SKIP LOCKED被評估**。使用不帶rownum的選擇,獲取一個(或更多根據需要)行並關閉光標,這是使用SKIP LOCKED的最佳方法。 – 2013-02-28 08:49:20

+0

的確,我不得不把選擇和更新放在一個事務中,現在它可以工作。謝謝!!! – 2013-02-28 09:12:07

2

每個進程在讀取行時都會發出SELECT ... FOR UPDATE來鎖定行。在這種情況下,進程A將讀取並鎖定該行,進程B將嘗試讀取該行並阻塞,直到進程A通過提交(或回滾)其事務來釋放鎖。然後,Oracle將確定該行是否仍然符合B的標準,並且在您的示例中不會將該行返回給B.這適用,但這意味着您的多線程進程現在可能實際上是單線程的,這取決於您的事務控制方式需要工作。

可能的方式來提高可伸縮性

對消費者
  • 一種比較常見的方法來解決,這是有一個協調線程從表中讀取數據,包裹出工作不同的線程,並更新表格(包括知道如何重新分配作業,如果分配的線程已經死亡)。
  • 如果您使用的是Oracle 11.1或更高版本,則可以在FOR UPDATE上使用SKIP LOCKED clause,以便每個會話取回符合其標準且未鎖定的第一行(該子句存在於早期版本中,但沒有記錄,因此它未被記錄可能無法正常工作)。
  • 您可以使用具有多個使用者的隊列,而不是使用ImportJobs的表格。這將允許Oracle將消息分發到每個進程,而無需構建任何額外的鎖定(Oracle隊列在幕後執行)。
1

使用versioning and optimistic concurrency

IMPORTJOBS表應該有一個時間戳列,在模型中標記爲ConcurrencyMode = Fixed。現在,當EF嘗試進行更新時,時間戳列被包含在更新語句中:WHERE timestamp = xxxxx

對於B,時間戳同時發生了變化,因此引發了一個併發異常,在這種情況下,您通過跳過更新來處理該異常。

我來自SQL服務器的背景,我不知道甲骨文的時間戳(或rowversion)等價物,但想法是它是一個字段,當更新記錄時自動更新。