Nie możesz użyć do tego sekwencji. Potrzebujesz jednego punktu serializacji, przez który wszystkie wstawki muszą zostać usunięte - w przeciwnym razie nie można zagwarantować atrybutu „bez przerw”. Musisz także upewnić się, że żadne wiersze nigdy nie zostaną usunięte z tej tabeli.
Serializacja oznacza również, że tylko jedna transakcja może wstawić wiersze do tej tabeli — wszystkie inne wstawienia muszą poczekać, aż „poprzednie” wstawienie zostanie zatwierdzone lub wycofane.
Jednym ze wzorców, w jaki sposób można to zaimplementować, jest posiadanie tabeli, w której przechowywane są numery „sekwencji”. Załóżmy, że potrzebujemy tego dla numerów faktur, które muszą być bez przerw ze względów prawnych.
Dlatego najpierw tworzymy tabelę do przechowywania „bieżącej wartości”:
create table slow_sequence
(
seq_name varchar(100) not null primary key,
current_value integer not null default 0
);
-- create a "sequence" for invoices
insert into slow_sequence values ('invoice');
Teraz potrzebujemy funkcji, która wygeneruje następną liczbę, ale która gwarantuje, że żadne dwie transakcje nie mogą uzyskać kolejnego numeru w tym samym czasie.
create or replace function next_number(p_seq_name text)
returns integer
as
$$
update slow_sequence
set current_value = current_value + 1
where seq_name = p_seq_name
returning current_value;
$$
language sql;
Funkcja zwiększy licznik i zwróci w rezultacie zwiększoną wartość. Z powodu update
wiersz sekwencji jest teraz zablokowany i żadna inna transakcja nie może zaktualizować tej wartości. Jeśli transakcja wywołująca zostanie wycofana, tak samo jest z aktualizacją licznika sekwencji. Jeśli zostanie zatwierdzony, nowa wartość zostanie zachowana.
Aby upewnić się, że każdy transakcja korzysta z tej funkcji, należy utworzyć wyzwalacz.
Utwórz odpowiednią tabelę:
create table invoice
(
invoice_number integer not null primary key,
customer_id integer not null,
due_date date not null
);
Teraz utwórz funkcję wyzwalacza i wyzwalacz:
create or replace function f_invoice_trigger()
returns trigger
as
$$
begin
-- the number is assigned unconditionally so that this can't
-- be prevented by supplying a specific number
new.invoice_number := next_number('invoice');
return new;
end;
$$
language plpgsql;
create trigger invoice_trigger
before insert on invoice
for each row
execute procedure f_invoice_trigger();
Teraz, jeśli jedna transakcja to zrobi:
insert into invoice (customer_id, due_date)
values (42, date '2015-12-01');
Generowany jest nowy numer. sekunda transakcja musi wtedy poczekać, aż pierwsza wstawka zostanie zatwierdzona lub wycofana.
Jak powiedziałem:to rozwiązanie nie jest skalowalne. Zupełnie nie. Spowoduje to znaczne spowolnienie aplikacji, jeśli w tej tabeli będzie dużo wstawek. Ale nie możesz mieć obu:skalowalnego i poprawna implementacja sekwencji bez przerw.
Jestem również prawie pewien, że istnieją przypadki brzegowe, których nie obejmuje powyższy kod. Więc jest całkiem prawdopodobne, że nadal możesz skończyć z lukami.