2016-08-12 7 views
8

фона«Юридический» определенная последовательность

У меня есть много разных «вещи» (домен конкретный элемент/объект/при условии), которые видны «вещь» владельцы (люди). Владельцы собираются идентифицировать свои «вещи» с числом. Вместо того, чтобы показывать большое «случайное» число, я хочу показать им небольшое число (предпочтительно последовательность, начинающуюся с 1), которая проще для людей. Владельцам очень удобно говорить о «моем foo 37» и «ее баре 128». Там «последовательность» может иметь пробелы, но прикрепленный номер должен оставаться неизменным во время жизни «вещи». Поэтому мне нужен способ генерировать «вещь» + собственный идентификатор владельца (в настоящее время называемый «видимым идентификатором»).

Количество комбинаций «вещь» + владелец в масштабе 10k +. В настоящее время новые «вещи» не могут динамически генерироваться, но владельцы могут быть.

Количество экземпляров «штуки» на одного владельца относительно невелико, примерно десяток на одного владельца, но нет жесткой крышки, которая может быть получена из бизнес-правил. Новые экземпляры «вещи» создаются и удаляются часто.

Рассматриваемые варианты

Я нашел хорошее обсуждение в SO вопрос Oracle Partitioned Sequence, что обращается в значительной степени тот же вопрос, у меня есть.

До сих пор я рассмотрел следующие варианты:

  1. Я думаю, что стандартная последовательность базы данных будет прекрасно, но это потребовало бы мне динамически создавать большое количество «вещь» + владелец специфических последовательностей и также разрешает имя последовательности во время вставки. (И отбросьте последовательности, когда владелец ушел.) Я не уверен, что создание огромного количества последовательностей - хорошая практика вообще (мне 10 000 + объектов базы данных - огромное количество).
  2. Я также считал печально известным max(visible_id) + 1, но мы столкнемся с обычными проблемами параллелизма с этим, так что это не-go.
  3. Не сохраняйте отдельный идентификатор владельца в базе данных, а вместо этого создавайте его в виде suggested по Adam Musch. Это замечательная идея, но, к сожалению, идентификатор должен быть таким же во время жизни экземпляра «вещь».
  4. Избегайте всей проблемы, позволяя владельцам назвать «вещь». Но им совсем не понравилась идея - «Почему я должен беспокоиться, так легко сказать foo 16.»!

Вопрос

Есть ли какой-либо другой способ решения этой проблемы, или я должен начать создавать последовательности динамически? Если последовательности являются ответом, пожалуйста, уточните, какие подводные камни могут быть (например, неявные фиксации в DDL).

Меня интересуют как решения Oracle 11gR2, так и 12c (если они разные).

Псевдо-код для иллюстрации постановка вопроса

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

Почему опция 3 не применяется? Какие экземпляры могут быть удалены? Что, если пометить их как удаленные (например, через дополнительный столбец)? –

+1

Я не понимаю, почему # 2 создаст проблемы параллелизма, когда MAX (visible_id) привязан к определенному владельцу (вам определенно не нужно макс. Всей таблицы). Возможно ли, что владелец будет обновлять свои товары в одно и то же время из более чем одного экземпляра приложения? Похоже, вам нужен первичный ключ на столе, владелец, штука. Thingid должен быть сгенерирован вами, поэтому я бы предложил использовать хранимую процедуру для обновления/вставки/удаления, чтобы вы могли управлять последовательностью, но вы хотите вместо прямого вызова операций dml. – Matt

+0

@ KonstantinSorokin Ну ... потому что ... Я просто могу сказать, что я был слеп для этого варианта. Да, я знаю о [soft delete] (http://stackoverflow.com/q/2549839/272735), и да, моя первая идея в том, что здесь может быть неплохо. Мне нужно больше обдумать эту идею, преимущества и недостатки. – user272735

ответ

2

Поскольку зазоры в порядке, вы должны реализовать вариант «вариант 2».Разрешение пробелов означает, что ваша синхронизация может быть выполнена быстро: конкурирующие сессии просто проверяют и перемещаются, вместо того, чтобы ждать, чтобы убедиться, что другие совершают или откатываются.

Если Oracle предложила вариант INSERT INTO..NOWAIT, это было бы легко. Как бы то ни было, я бы, вероятно, включил DBMS_LOCK. Вот мой взгляд на то, как будет выглядеть ваш API.

Он делает некоторые предположения о максимальном видимом идентификаторе, который у вас есть, потому что вы сделали эти предположения в своем исходном посте.

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

Я только сегодня смог взглянуть на этот ответ. Это выглядит как идеальное совпадение, и я буду реализовывать последовательность, специфичную для сущности, таким образом. В качестве бонуса это потенциально может быть использовано для ограничения количества объектов, если я столкнулся с таким требованием. Благодаря ! – user272735

Смежные вопросы