Можно ли заблокировать строки в курсоре, если курсор возвращает только одну строку со счетчиком (*)? - PullRequest
4 голосов
/ 18 июня 2010

Я бы хотел запретить пользователям вставлять более 3 записей с color = 'Red' в мою таблицу FOO.Мои намерения состоят в том, чтобы А) получить текущий счетчик, чтобы я мог определить, разрешена ли другая запись, и Б) запретить другим процессам вставлять какие-либо красные записи, пока эта находится в процессе, следовательно, for update of.

Я хотел бы сделать что-то вроде:

cursor cur_cnt is
select count(*) cnt from foo
where foo.color = 'Red'
for update of foo.id;

Будет ли это удовлетворять обоим моим требованиям или не заблокировать только те строки в подсчете (*), у которых было foo.color = 'Red'?

Ответы [ 4 ]

4 голосов
/ 18 июня 2010

Это только запретит пользователям обновлять выбранные строки, а не добавлять новые. Единственный способ надежно обеспечить соблюдение такого правила - это комбинация проверочного ограничения (в «главной» таблице) и триггера в таблице «foo», который обновляет основную таблицу. Примерно так (используя EMP и DEPT для знакомства):

alter table dept add (manager_count integer default 0 not null,
   constraint manager_count_chk check (manager_count <= 3));

create trigger emp_trg
before insert or update or delete on emp
for each row
begin
    if inserting or updating then
        if :new.job = 'MANAGER' then
            update dept
            set    manager_count = manager_count+1
            where  deptno = :new.deptno;
        end if;
    end if;
    if updating or deleting then
        if :old.job = 'MANAGER' then
            update dept
            set    manager_count = manager_count-1
            where  deptno = :new.deptno;
        end if;
    end if;
end;

Это обеспечивает требуемую блокировку, предотвращая одновременное добавление, обновление или удаление сотрудников «МЕНЕДЖЕР» более чем одним пользователем.

3 голосов
/ 18 июня 2010

Блокировка существующих строк не может помешать другим сеансам вставлять новые строки.

Один из возможных подходов - иметь таблицы COLORS, в которых перечислены возможные цвета. (Тогда ваш FOO.COLOR может иметь ссылку на внешний ключ для COLORS.COLOR.) Затем заблокируйте соответствующую строку в COLOURS перед выполнением вставок и обновлений. Это будет сериализовать все обращения, которые имеют один и тот же цвет.

1 голос
/ 18 июня 2010

Какую базу данных вы используете? Например, в DB2 вы можете несколько контролировать поведение блокировки, добавляя «WITH [уровень блокировки]», тогда как уровень блокировки является одним из 4 предопределенных уровней блокировки. В целом, однако, я бы не предполагал, что база данных будет блокировать вещи точно так же, как вы делаете отступ, - есть также такие вещи, как повышение блокировки. Если вы хотите запретить вставку каких-либо данных в таблицу, снова в DB2 вы можете сделать 'LOCK TABLE table IN EXCLUSIVE MODE'.

0 голосов
/ 18 июня 2010

Вы можете использовать Oracle CONTEXT, чтобы сохранить ИМЯ пользователя Oracle, который пытается вставить / обновить / удалить в таблицу FOO.Вы очищаете (вручную) Oracle CONTEXT, когда пользователь Oracle фиксирует или выполняет откат, используя мои хранимые процедуры USER_LOCK и USER_UNLOCK.Таким образом, вы можете избежать одновременных вставок / обновлений и удалений от более чем одного пользователя Oracle, поскольку вы одновременно предоставляете доступ только одному пользователю Oracle.Вы можете добавить имя пользователя Oracle в контексте Oracle.С помощью процедуры USER_UNLOCK (name_of_the_user) вы можете удалить имя пользователя Oracle в контексте Oracle.Используя представление locked_users, вы можете определить, заблокирован пользователь Oracle или нет, потому что если он заблокирован, его имя отображается в представлении.Следующий код создает все структуры Oracle для достижения всего этого:

CREATE OR REPLACE PACKAGE my_pkg
IS
   PROCEDURE set_session_id (p_session_id NUMBER);

   PROCEDURE set_ctx (p_name VARCHAR2, p_value VARCHAR2);

   PROCEDURE close_session (p_session_id NUMBER);
END;
/

CREATE OR REPLACE PACKAGE BODY my_pkg
IS
   g_session_id   NUMBER;

   PROCEDURE set_session_id (p_session_id NUMBER)
   IS
   BEGIN
      g_session_id := p_session_id;
      DBMS_SESSION.set_identifier (p_session_id);
   END set_session_id;

   --===============================================
   PROCEDURE set_ctx (p_name VARCHAR2, p_value VARCHAR2)
   IS
   BEGIN
      DBMS_SESSION.set_context ('App_Ctx',
                                p_name,
                                p_value,
                                USER,
                                g_session_id);
   END set_ctx;

   --===============================================
   PROCEDURE close_session (p_session_id NUMBER)
   IS
   BEGIN
      DBMS_SESSION.set_identifier (p_session_id);
      DBMS_SESSION.clear_identifier;
   END close_session;
--===============================================
END;
/

    CREATE OR REPLACE CONTEXT APP_CTX
     USING MY_PKG
     ACCESSED GLOBALLY
/


CREATE OR REPLACE TYPE test_type AS TABLE OF VARCHAR2 (30);
/



CREATE OR REPLACE FUNCTION f_convert2 (p_list IN VARCHAR2)
   RETURN test_type
   PIPELINED
AS
   --l_string        LONG        := p_list || ',';
   l_string        VARCHAR2 (4000) := p_list || ',';
   l_comma_index   PLS_INTEGER;
   l_index         PLS_INTEGER := 1;
BEGIN
   LOOP
      l_comma_index := INSTR (l_string, ',', l_index);
      EXIT WHEN l_comma_index = 0;
      PIPE ROW (SUBSTR (l_string, l_index, l_comma_index - l_index));
      l_index := l_comma_index + 1;
   END LOOP;

   RETURN;
END f_convert2;
/



CREATE OR REPLACE FORCE VIEW locked_users (utente)
AS
     SELECT COLUMN_VALUE utente
       FROM TABLE (
               f_convert2 (
                  REPLACE (
                     LTRIM (RTRIM (SYS_CONTEXT ('app_ctx', 'Var1', 4000), '*'),
                            '*'),
                     '**',
                     ',')))
   ORDER BY 1 ASC
/



CREATE OR REPLACE PROCEDURE user_lock (ne_user IN VARCHAR2)
IS
BEGIN
   DECLARE
      indice                NUMBER;
      appoggio_variabile1   VARCHAR2 (250);
   BEGIN
      -- my_pkg.close_session(1234);
      my_pkg.set_session_id (1234);
      appoggio_variabile1 := SYS_CONTEXT ('app_ctx', 'var1');
      DBMS_OUTPUT.put_line (appoggio_variabile1);

      IF INSTR (appoggio_variabile1, ne_user) >= 1
      THEN
         BEGIN
            DBMS_OUTPUT.
             put_line ('The user ' || ne_user || ' is already locked!');
         END;
      ELSE
         BEGIN
            my_pkg.
             set_ctx ('Var1', appoggio_variabile1 || '*' || ne_user || '*');
            DBMS_OUTPUT.
             put_line ('The user ' || ne_user || ' is now locked.');
         END;
      END IF;
   END;
END user_lock;
/



CREATE OR REPLACE PROCEDURE user_unlock (ne_user IN VARCHAR2)
IS
BEGIN
   DECLARE
      indice                NUMBER;
      appoggio_variabile1   VARCHAR2 (250);
   BEGIN
      -- my_pkg.close_session(1234);
      my_pkg.set_session_id (1234);
      appoggio_variabile1 := SYS_CONTEXT ('app_ctx', 'var1');
      DBMS_OUTPUT.put_line (appoggio_variabile1);

      IF INSTR (appoggio_variabile1, ne_user) = 0
         OR appoggio_variabile1 IS NULL
      THEN
         BEGIN
            DBMS_OUTPUT.
             put_line ('The user ' || ne_user || ' is already unlocked!');
         END;
      ELSE
         BEGIN
            my_pkg.
             set_ctx ('Var1',
                      REPLACE (appoggio_variabile1, '*' || ne_user || '*'));
            DBMS_OUTPUT.
             put_line ('The user ' || ne_user || ' is now unlocked.');
         END;
      END IF;
   END;
END user_unlock;
/
...