2016-08-12 20 views
8

nềnAn "thực thể" chuỗi cụ thể

Tôi có rất nhiều "thứ" khác nhau (một lĩnh vực cụ thể mục/tổ chức/đối tượng) mà có thể nhìn thấy những "điều" chủ sở hữu (con người). Chủ sở hữu sẽ xác định "thứ" của họ với một số. Thay vào đó cho thấy một số "ngẫu nhiên" lớn, tôi muốn chỉ cho họ một số nhỏ (tốt hơn là một chuỗi bắt đầu từ 1) dễ dàng hơn cho con người. Các chủ sở hữu rất thoải mái khi nói về "foo 37 của tôi" và "thanh của cô ấy 128". Có "trình tự" có thể có những khoảng trống nhưng số lượng kèm theo phải giữ nguyên trong suốt thời gian tồn tại của "sự cố". Vì vậy, tôi cần một cách để tạo ra "điều" + id chủ sở hữu cụ thể (hiện được gọi là "id hiển thị").

Số lượng "điều" + kết hợp chủ sở hữu là về quy mô của 10k +. Hiện tại "những thứ" mới không thể được tạo động nhưng chủ sở hữu có thể.

Số một "điều" các trường hợp mỗi chủ sở hữu là một tương đối nhỏ, khoảng chục mỗi chủ sở hữu, nhưng không có nắp cứng có thể được bắt nguồn từ các quy tắc kinh doanh. Trường hợp "điều" mới được tạo và xóa thường xuyên.

tùy chọn coi

Tôi tìm thấy một cuộc thảo luận tốt trong một SO câu hỏi Oracle Partitioned Sequence nhằm giải quyết khá nhiều vấn đề tương tự tôi có.

Cho đến nay tôi đã xem xét các tùy chọn sau:

  1. Tôi nghĩ rằng một chuỗi cơ sở dữ liệu tiêu chuẩn sẽ là một hoàn toàn tốt đẹp nhưng điều đó sẽ yêu cầu tôi để tự động tạo ra một số lượng lớn các "điều" trình tự cụ thể + chủ sở hữu và cũng giải quyết tên trình tự trong quá trình chèn. (Và thả các chuỗi khi chủ sở hữu đã biến mất.) Tôi không chắc chắn nếu tạo một số lượng lớn các chuỗi là một thực hành tốt ở tất cả (đối với tôi 10k + đối tượng cơ sở dữ liệu là một số lượng lớn).
  2. Tôi cũng coi khét tiếng max(visible_id) + 1 nhưng chúng tôi sẽ chạy vào các vấn đề đồng thời bình thường với điều đó, vì vậy đó là một không-go.
  3. Không lưu id chủ sở hữu cụ thể vào cơ sở dữ liệu ở tất cả nhưng thay vì tạo ra nó trong các lựa chọn như suggested bởi Adam Musch. Đây là một ý tưởng tuyệt vời nhưng tiếc là id cần phải giống nhau trong suốt cuộc đời "điều".
  4. Tránh toàn bộ vấn đề bằng cách cho phép các chủ sở hữu tên "điều". Nhưng họ không thích ý tưởng gì cả - "Tại sao tôi nên bận tâm, thật dễ dàng để nói foo 16."!

Câu hỏi

Có một số cách khác để giải quyết vấn đề này hay tôi nên bắt đầu tạo các chuỗi động? Nếu các chuỗi là câu trả lời, hãy giải thích những gì có thể xảy ra (giống như các cam kết ngầm định trong DDL).

Tôi quan tâm đến cả hai Oracle 11gR2 và 12c giải pháp (nếu họ là khác nhau).

Pseudo-code để minh họa cho câu hỏi

create table foo (
id number primary key -- the key for computers 
,owner_id number 
,visible_id number -- the key for humans 
,data_ varchar2(20) 
); 

create constraint foo_u1 unique foo(owner_id, visible_id); 

-- primary key sequence 
create sequence foo_id_seq; 

insert into foo values(
foo_id_seq.nextval 
,1 
,1 -- what to put here ? 
,'lorem ipsum' 
); 

insert into foo values(
foo_id_seq.nextval 
,2 
,1 -- what to put here ? 
,'dolor sit amet' 
); 

select visible_id, data_ from foo where owner = 2 order by visible_id; 
+1

Tại sao tùy chọn 3 không áp dụng? Các trường hợp nào có thể bị xóa? Điều gì sẽ xảy ra nếu đánh dấu chúng là đã xóa (thông qua cột bổ sung chẳng hạn)? –

+1

Tôi không hiểu tại sao # 2 sẽ tạo ra các vấn đề tương tranh khi MAX (visible_id) bị ràng buộc với một chủ sở hữu cụ thể (bạn chắc chắn không muốn tối đa toàn bộ bảng). Có khả năng chủ sở hữu sẽ cập nhật các mặt hàng của họ tại cùng một thời điểm chính xác từ hơn một phiên bản ứng dụng? Có vẻ như bạn cần một khóa chính trên bàn, chủ nhân, thingid. Thingid sẽ phải được tạo ra bởi bạn vì vậy tôi sẽ đề nghị sử dụng một thủ tục lưu trữ để cập nhật/chèn/xóa để bạn có thể quản lý trình tự tuy nhiên bạn muốn thay vì gọi dml hoạt động trực tiếp – Matt

+0

@KonstantinSorokin Vâng ... bởi vì, err ... Tôi có thể đơn giản là tôi bị mù vì lựa chọn đó. Có, tôi biết [xóa mềm] (http://stackoverflow.com/q/2549839/272735) và ý tưởng đầu tiên của tôi là có thể phù hợp ở đây. Tôi cần phải suy nghĩ nhiều hơn về ý tưởng này và những lợi ích và hạn chế. – user272735

Trả lời

2

Kể từ những khoảng trống là OK, bạn nên thực hiện một biến thể của "phương án 2".Cho phép các khoảng trống có nghĩa là việc đồng bộ hóa của bạn có thể được thực hiện nhanh chóng: với các phiên cạnh tranh chỉ đơn thuần là kiểm tra và di chuyển thay vì phải đợi để xem những người khác có cam kết hay quay lại hay không.

Nếu Oracle cung cấp tùy chọn INSERT INTO..NOWAIT, điều này sẽ dễ dàng. Như mọi thứ, tôi có thể liên quan đến DBMS_LOCK. Đây là cách tôi xem xét API của bạn.

Nó đưa ra một số giả định về ID hiển thị tối đa mà bạn có bởi vì bạn đã đưa ra các giả định đó trong bài đăng gốc của mình.

CREATE OR REPLACE PACKAGE foo_api AS 
    PROCEDURE create_foo (p_owner_id NUMBER, p_data VARCHAR2); 
END foo_api; 

CREATE OR REPLACE PACKAGE BODY foo_api AS 
    -- We need to call allocate_unique in an autonomous transaction because 
    -- it commits and the calling program may not want to commit at this time 
    FUNCTION get_lock_handle (p_owner_id NUMBER, p_visible_id NUMBER) 
    RETURN VARCHAR2 IS 
    PRAGMA AUTONOMOUS_TRANSACTION; 
    l_lock_handle VARCHAR2 (128); 
    BEGIN 
    DBMS_LOCK.allocate_unique (
     lockname => 'INSERT_FOO_' || p_owner_id || '_' || p_visible_id, 
     lockhandle => l_lock_handle 
    ); 
    COMMIT; 
    RETURN l_lock_handle; 
    END; 


    PROCEDURE create_foo (p_owner_id NUMBER, p_data VARCHAR2) IS 
    -- This is the highest visible ID you'd ever want. 
    c_max_visible_id NUMBER := 1000; 
    BEGIN 
    <<id_loop>> 
    FOR r_available_ids IN (SELECT a.visible_id 
          FROM (SELECT ROWNUM visible_id 
            FROM DUAL 
            CONNECT BY ROWNUM <= c_max_visible_id) a 
            LEFT JOIN foo 
            ON foo.owner_id = p_owner_id 
            AND foo.visible_id = a.visible_id 
          WHERE foo.visible_id IS NULL) LOOP 
     -- We found a gap 
     -- We could try to insert into it. If another session has already done so and 
     -- committed, we'll get an ORA-00001. If another session has already done so but not 
     -- yet committed, we'll wait. And waiting is bad. 
     -- We'd like an INSERT...NO WAIT, but Oracle doesn't provide that. 
     -- Since this is the official API for creating foos and we have good application 
     -- design to ensure that foos are not created outside this API, we'll manage 
     -- the concurrency ourselves. 
     -- 
     -- Try to acquire a user lock on the key we're going to try an insert. 
     DECLARE 
     l_lock_handle  VARCHAR2 (128); 
     l_lock_result  NUMBER; 
     l_seconds_to_wait NUMBER := 21600; 
     BEGIN 
     l_lock_handle := get_lock_handle (
      p_owner_id => p_owner_id, 
      p_visible_id => r_available_ids.visible_id 
     ); 

     l_lock_result := DBMS_LOCK.request (lockhandle => l_lock_handle, 
              lockmode => DBMS_LOCK.x_mode, 
              timeout => 0, -- Do not wait 
              release_on_commit => TRUE); 

     IF l_lock_result = 1 THEN 
      -- 1 => Timeout -- this could happen. 
      -- In this case, we want to move onto the next available ID. 
      CONTINUE id_loop; 
     END IF; 

     IF l_lock_result = 2 THEN 
      -- 2 => Deadlock (this should never happen, but scream if it does). 
      raise_application_error (
      -20001, 
       'A deadlock occurred while trying to acquire Foo creation lock for ' 
      || p_owner_id 
      || '_' 
      || r_available_ids.visible_id 
      || '. This is a programming error.'); 
     END IF; 

     IF l_lock_result = 3 THEN 
      -- 3 => Parameter error (this should never happen, but scream if it does). 
      raise_application_error (
      -20001, 
       'A parameter error occurred while trying to acquire Foo creation lock for ' 
      || p_owner_id 
      || '_' 
      || r_available_ids.visible_id 
      || '. This is a programming error.'); 
     END IF; 

     IF l_lock_result = 4 THEN 
      -- 4 => Already own lock (this should never happen, but scream if it does). 
      raise_application_error (
      -20001, 
       'Attempted to create a Foo creation lock and found lock already held by session for ' 
      || p_owner_id 
      || '_' 
      || r_available_ids.visible_id 
      || '. This is a programming error.'); 
     END IF; 

     IF l_lock_result = 5 THEN 
      -- 5 => Illegal lock handle (this should never happen, but scream if it does). 
      raise_application_error (
      -20001, 
       'An illegal lock handle error occurred while trying to acquire Foo creation lock for ' 
      || p_owner_id 
      || '_' 
      || r_available_ids.visible_id 
      || '. This is a programming error.'); 
     END IF; 
     END; 

     -- If we get here, we have an exclusive lock on the owner_id/visible_id 
     -- combination. Attempt the insert 
     BEGIN 
     INSERT INTO foo (id, 
         owner_id, 
         visible_id, 
         data_) 
     VALUES (foo_id_seq.NEXTVAL, 
       p_owner_id, 
       r_available_ids.visible_id, 
       p_data); 

     -- If we get here, we are done. 
     EXIT id_loop; 
     EXCEPTION 
     WHEN DUP_VAL_ON_INDEX THEN 
      -- Unfortunately, if this happened, we would have waited until the competing 
      -- session committed or rolled back. But the only way it 
      -- could have happened if the competing session did not use our API to create 
      -- or update the foo. 
      -- TODO: Do something to log or alert a programmer that this has happened, 
      -- but don't fail. 
      CONTINUE id_loop; 
     END; 
    END LOOP; 
    END create_foo; 
END foo_api; 
+0

Hôm nay tôi chỉ có thể có một cái nhìn về câu trả lời này. Điều này trông giống như một trận đấu hoàn hảo và tôi sẽ thực hiện trình tự cụ thể theo cách này. Là tiền thưởng, điều này có thể được sử dụng để giới hạn số lượng thực thể nếu tôi chạy vào yêu cầu đó. Cảm ơn ! – user272735

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