Du kan inte använda en sekvens för detta. Du behöver en enda serialiseringspunkt genom vilken alla inlägg måste gå - annars kan attributet "gapless" inte garanteras. Du måste också se till att inga rader någonsin kommer att raderas från den tabellen.
Serialiseringen innebär också att endast en enda transaktion kan infoga rader i den tabellen - alla andra insättningar måste vänta tills den "föregående" infogningen har genomförts eller rullats tillbaka.
Ett mönster för hur detta kan implementeras är att ha en tabell där "sekvens"-numren lagras. Låt oss anta att vi behöver detta för fakturanummer som måste vara fria av juridiska skäl.
Så vi skapar först tabellen för att hålla det "aktuella värdet":
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');
Nu behöver vi en funktion som kommer att generera nästa nummer men som garanterar att inga två transaktioner kan få nästa nummer samtidigt.
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;
Funktionen ökar räknaren och returnerar det inkrementerade värdet som ett resultat. På grund av update
raden för sekvensen är nu låst och ingen annan transaktion kan uppdatera det värdet. Om den anropande transaktionen rullas tillbaka, så är uppdateringen av sekvensräknaren också. Om det har begåtts, kvarstår det nya värdet.
För att säkerställa att varje transaktion använder funktionen, en trigger bör skapas.
Skapa tabellen i fråga:
create table invoice
(
invoice_number integer not null primary key,
customer_id integer not null,
due_date date not null
);
Skapa nu triggerfunktionen och triggern:
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();
Om nu en transaktion gör detta:
insert into invoice (customer_id, due_date)
values (42, date '2015-12-01');
Det nya numret genereras. En sekund transaktionen måste sedan vänta tills den första infogningen har genomförts eller återställts.
Som sagt:den här lösningen är inte skalbar. Inte alls. Det kommer att sakta ner din applikation enormt om det finns många inlägg i den tabellen. Men du kan inte ha både och:ett skalbart och korrekt implementering av en luckfri sekvens.
Jag är också ganska säker på att det finns edge case som inte omfattas av ovanstående kod. Så det är ganska troligt att du fortfarande kan sluta med luckor.