Oracle: создать последовательность сеансов?

У меня есть таблица следующим образом

Таблица содержит пользователей моего приложения и хранит их клиентов. Столбец User Client ID относится к внешнему ключу, связанному с другой таблицей, в которой хранятся сведения о клиентах.

введите здесь описание изображения

Мне нужен еще один столбец (Счетчик клиентов пользователя), который является просто счетчиком клиентов каждого пользователя. Мне нужно, чтобы он начинался с 1 и увеличивался для каждого отдельного пользователя приложения.

На данный момент я заполняю это, подсчитывая количество клиентов для каждого пользователя + 1, прежде чем вставлять новую строку в таблицу:

    select count(*) + 1 into MyVariable from Mytable where UserClientId = Something

Затем я использую MyVariable в столбце User Client Counter.

Этот метод работает довольно хорошо, но если пользователь подключен из двух разных сеансов, запрос может дать неправильное количество счетчиков... в дополнение к этому производительность может быть плохой в случае больших таблиц...

Есть ли лучший способ заменить такой процесс с помощью последовательностей?

Я искал последовательности сеансов, но они сбрасываются после окончания каждого сеанса.

(Этот столбец необходим для бизнеса и не может быть заменен чем-то вроде rownumber в запросах на восстановление. Поскольку каждый клиент должен всегда сохранять один и тот же идентификатор для пользователя приложения)

Заранее спасибо.

Ваше здоровье,


person Thomas Carlton    schedule 28.10.2020    source источник
comment
Зачем вам это нужно в первую очередь? Будет ли где-то открыт столбец User Client Counter? Почему бы не использовать уникальную нумерацию для всех компаний? Я пытаюсь выяснить вариант использования.   -  person The Impaler    schedule 28.10.2020
comment
Каждому пользователю приложения необходимо видеть несколько простых идентификаторов для своих клиентов (Клиент 1, Клиент 2, Клиент 3...). Не глобальный идентификатор, который может быть для него бессмысленным.   -  person Thomas Carlton    schedule 28.10.2020
comment
Я бы использовал процесс в очереди либо внутри приложения, либо один экземпляр, работающий на отдельном сервере. Вы запускаете один экземпляр или несколько? Это решает проблему централизованного УНИКАЛЬНОГО процесса, который синхронизирует важные задачи, такие как эта. Затем каждый раз, когда вам нужно вставить новую строку, этот процесс может предоставить для нее новое значение или даже вставить строку.   -  person The Impaler    schedule 28.10.2020
comment
Если вам нравится pl/sql, вы можете использовать синхронизированный блок для одновременного выполнения. См. stackoverflow.com/questions/17321687/.   -  person The Impaler    schedule 28.10.2020
comment
Каждый пользователь приложения должен видеть несколько простых идентификаторов для своих клиентов. Каково бизнес-определение клиента в этом контексте? Если все, что вам нужно, это простой идентификатор, почему бы не использовать client_id? Почему «глобальный идентификатор» не имеет смысла? В любом случае, он может видеть только своих собственных.   -  person EdStevens    schedule 28.10.2020
comment
@TheImpaler, не могли бы вы объяснить, что вы подразумеваете под процессом в очереди? Спасибо   -  person Thomas Carlton    schedule 29.10.2020
comment
Под процессом в очереди я подразумеваю процесс, который прослушивает очередь сообщений. Это может быть полноценный отдельный сервер, слушающий множество других серверов, или простая внутренняя синхронизированная очередь (например, в Java это может быть BlockingQueue‹E›). Каждый раз, когда параллельному потоку необходимо произвести новое число, он будет просить этот централизованный процесс вычислить его; столкновений больше не будет. Или... вы можете использовать синхронизированную процедуру/функцию PL/SQL, как я упоминал ранее. Либо будет работать. Кроме того, вы можете использовать кэширование, чтобы ускорить процесс и не запускать SELECT MAX(id) каждый раз.   -  person The Impaler    schedule 29.10.2020


Ответы (3)


Я думаю, вы можете просто создать уникальный индекс для пользователя приложения и текущего номера:

create unique index idx on mytable (app_user_id, num);

А затем вставьте с max + 1:

insert into mytable (app_user_id, client_id, num)
values 
(
  :app_user_id, 
  :client_id,
  coalesce((select max(num) + 1 from mytable where app_user_id = :app_user_id), 1)
);
person Thorsten Kettner    schedule 28.10.2020
comment
И затем иметь обработку ошибок, когда этот скалярный подзапрос не получает правильный ответ (потому что он не может видеть строки в незафиксированной транзакции) - person Andrew Sayer; 28.10.2020
comment
Правильный. При одновременной записи вставка может завершиться ошибкой и ее необходимо будет запустить снова. Это идея с уникальным индексом. - person Thorsten Kettner; 28.10.2020
comment
Я все еще думаю, что синхронизированный процесс (даже написанный на pl/sql) лучше, чем обработка коллизий. - person The Impaler; 28.10.2020

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

Это очень просто, когда у вас есть таблица, в которой хранится информация на нужном уровне.

У вас может быть таблица ваших пользователей со столбцом счетчика, который начинается с 0.

MY_APPLICATION_USER                                CLIENT_COUNTER
-------------------------------------------------- --------------
Company X                                                       1
Company Y                                                       3
Company Z                                                       1

Когда вы вставляете строки в свою основную таблицу, вы сначала обновляете эту таблицу, устанавливая client_counter to be client_counter + 1 (вы делаете это как один оператор вставки, без рискованного выбора, а затем обновляете!), Затем вы возвращаете обновленное значение в свое значение для client_id. Все это можно сделать с помощью простого триггера.

create or replace trigger app_clients_ins
before insert 
on app_clients
for each row
declare
begin
  update app_users
  set    client_counter = client_counter + 1
  where  my_application_user = :new.my_application_user
  return client_counter into :new.user_client_number;
  
end;
/

Конечно, как и в любой последовательности, если вы удалите строку, она не позволит вашей следующей вставке заполнить этот пробел.

(db‹›fiddle https://dbfiddle.uk/?rdbms=oracle_18&fiddle=7f1b4f4b83316d59e=7f1b4f4b83316d59e )

person Andrew Sayer    schedule 28.10.2020

если вы хотите, чтобы были вставлены уникальные значения, и есть вероятность, что несколько пользователей могут вставлять строки в одну и ту же таблицу одновременно, тогда лучше использовать Oracle Sequence.

CREATE SEQUENCE id_seq INCREMENT BY 1;
INSERT INTO Mytable(id) VALUES (id_seq.nextval);

В вашем случае, я думаю, вы хотите создать разные последовательности для каждого клиента. Сколько у вас разных клиентов, если у вас их 100, то я не думаю, что создание последовательности будет работать, поскольку вам, возможно, придется создать столько последовательностей.

person hkandpal    schedule 28.10.2020