2013-02-27 31 views
5

Tôi có một cơ sở dữ liệu Oracle mà tôi truy cập bằng Devart và Entity Framework.Đọc và cập nhật đồng thời trong một bảng cơ sở dữ liệu

Có một bảng gọi là IMPORTJOBS với một cột STATUS.

Tôi cũng có nhiều quy trình đang chạy cùng một lúc. Mỗi người đọc hàng đầu tiên trong số IMPORTJOBS có trạng thái 'REGISTERED', đặt nó vào trạng thái 'EXECUTING' và nếu được đặt thành trạng thái 'EXECUTED'.

Bây giờ, vì các quá trình đang chạy song song, tôi tin rằng những điều sau có thể xảy ra:

  • quá trình A đọc hàng 10 trong đó có tình trạng REGISTERED,
  • quá trình B cũng đọc hàng 10 trong đó có vẫn tình trạng REGISTERED ,
  • quy trình Cập nhật hàng 10 đến trạng thái EXECUTING.

Quy trình B không thể đọc hàng 10 làm quy trình A đã đọc và sẽ cập nhật trạng thái của nó.

Tôi nên giải quyết vấn đề này bằng cách nào? Đặt đọc và cập nhật trong một giao dịch? Hoặc tôi nên sử dụng một số cách tiếp cận phiên bản hoặc cái gì khác?

Cảm ơn!

CHỈNH SỬA: nhờ câu trả lời được chấp nhận, tôi đã làm việc đó và ghi lại ở đây: http://ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database.

Trả lời

2

Bạn nên sử dụng cơ chế khóa tích hợp của cơ sở dữ liệu. Không phát minh lại bánh xe, đặc biệt là kể từ khi RDBMS được thiết kế để đối phó với tính đồng thời và nhất quán.

Trong Oracle 11g, tôi đề nghị bạn sử dụng tính năng SKIP LOCKED. Ví dụ mỗi quá trình có thể gọi một chức năng như thế này (giả sử id là số):

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; 

này sẽ trả lại 10 dòng (nếu có thể) không bị khóa. Các hàng này sẽ bị khóa và các phiên sẽ không chặn lẫn nhau.

Trong 10g và trước đó kể từ khi Oracle trả về kết quả nhất quán, hãy sử dụng FOR UPDATE một cách khôn ngoan và bạn không nên có vấn đề mà bạn mô tả. Ví dụ xem xét sau SELECT:

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

Điều gì sẽ xảy ra nếu tất cả các quá trình dự trữ hàng của họ với SELECT này? Điều đó ảnh hưởng đến kịch bản của bạn như thế nào:

  1. Phiên A nhận được 10 hàng không được xử lý.
  2. Phiên B sẽ nhận được 10 hàng tương tự, bị chặn và đợi phiên A.
  3. Phiên A cập nhật trạng thái của hàng đã chọn và cam kết giao dịch của hàng.
  4. Oracle bây giờ sẽ (tự động) chạy lại phiên B đã chọn từ đầu kể từ khi dữ liệu đã được sửa đổi và chúng tôi đã chỉ định FOR UPDATE (mệnh đề này buộc Oracle lấy phiên bản cuối cùng của khối).
    Điều này có nghĩa là phiên B sẽ nhận được 10 hàng mới.

Vì vậy, trong trường hợp này, bạn không có vấn đề nhất quán. Ngoài ra, giả định rằng giao dịch yêu cầu một hàng và thay đổi trạng thái của nó là nhanh chóng, tác động đồng thời sẽ là ánh sáng.

+0

cảm ơn bạn, tôi đang cố gắng thực thi "SELECT * FROM IMPORTJOBS WHERE STATUSCODE = 'ĐĂNG KÝ' VÀ ROWNUM <= 1 FOR UPDATE SKIP LOCKED", nhưng nó vẫn trả về cùng một hàng từ các quy trình khác nhau? –

+1

(1) đảm bảo rằng bạn đã tắt tính năng tự động tắt: bạn không thể khóa một hàng mà không có giao dịch. (2) 'CHO CẬP NHẬT SKIP LOCKED' và' rownum' [sẽ không hoạt động như bạn mong đợi] (http://stackoverflow.com/questions/5847228/oracle-select-for-update-behaviour) - điều này là bởi vì SKIP LOCKED được đánh giá ** sau ** mệnh đề WHERE. Sử dụng lựa chọn không có rownum, tìm nạp một hàng (hoặc nhiều hơn khi cần thiết) và đóng con trỏ, đó là cách tốt nhất để sử dụng SKIP LOCKED. –

+0

thực sự, tôi đã phải đặt lựa chọn và cập nhật trong một giao dịch, bây giờ nó hoạt động. Cảm ơn!!! –

2

Mỗi quá trình có thể phát hành SELECT ... FOR UPDATE để khóa hàng khi họ đọc. Trong trường hợp này, tiến trình A sẽ đọc và khóa hàng, tiến trình B sẽ cố đọc hàng và chặn cho đến khi tiến trình A giải phóng khóa bằng cách cam kết (hoặc lùi lại) giao dịch của nó. Sau đó, Oracle sẽ xác định xem hàng có đáp ứng được các tiêu chí của B hay không, trong ví dụ của bạn, sẽ không trả về hàng cho B. Điều này có nghĩa là quá trình đa luồng của bạn hiện có thể đơn luồng một cách hiệu quả tùy thuộc vào cách kiểm soát giao dịch của bạn cần phải làm việc.

cách có thể để cải thiện khả năng mở rộng

  • Một cách tiếp cận tương đối phổ biến trên người tiêu dùng để giải quyết này là để có một sợi phối duy nhất đọc dữ liệu từ bảng, bưu kiện hiện công việc cho chủ đề khác nhau, và cập nhật bảng một cách thích hợp (bao gồm cả việc biết cách chuyển nhượng lại một công việc nếu luồng đã được gán nó đã chết).
  • Nếu bạn đang sử dụng Oracle 11.1 trở lên, bạn có thể sử dụng SKIP LOCKED clause trên FOR UPDATE để mỗi phiên quay trở lại hàng đầu tiên đáp ứng tiêu chí của chúng và không bị khóa (mệnh đề tồn tại trong các phiên bản trước đó) có thể không hoạt động chính xác).
  • Thay vì sử dụng bảng cho ImportJobs, bạn có thể sử dụng hàng đợi với nhiều người tiêu dùng. Điều này sẽ cho phép Oracle phân phối các thông điệp tới từng tiến trình mà không cần phải xây dựng thêm bất kỳ khóa nào (hàng đợi của Oracle đang làm tất cả các cảnh sau).
1

Sử dụng versioning and optimistic concurrency.

Bảng IMPORTJOBS phải có cột dấu thời gian mà bạn đánh dấu là ConcurrencyMode = Fixed trong mô hình của bạn. Bây giờ khi EF cố gắng cập nhật cột dấu thời gian được kết hợp trong câu lệnh cập nhật: WHERE timestamp = xxxxx.

Đối với B, dấu thời gian thay đổi trong thời gian trung bình, vì vậy ngoại lệ đồng thời được nêu lên, trong trường hợp này, bạn xử lý bằng cách bỏ qua cập nhật.

Tôi từ nền máy chủ SQL và tôi không biết tương đương với dấu thời gian (hoặc hàng tuần) của Oracle, nhưng ý tưởng là một trường tự động cập nhật khi bản cập nhật được tạo thành bản ghi.

Các vấn đề liên quan