Dit lijkt op een variant van het gapless sequence-probleem; ook hier te zien.
Gapless sequenties hebben ernstige prestatie- en gelijktijdigheidsproblemen.
Denk heel goed na over wat er zal gebeuren als er meerdere inserties tegelijk plaatsvinden. U moet voorbereid zijn om mislukte invoegingen opnieuw te proberen, of LOCK TABLE myTable IN EXCLUSIVE MODE
voor de INSERT
dus slechts één INSERT
kan tegelijk vliegen.
Gebruik een volgordetabel met rijvergrendeling
Wat ik in deze situatie zou doen is:
CREATE TABLE sequence_numbers(
level integer,
code integer,
next_value integer DEFAULT 0 NOT NULL,
PRIMARY KEY (level,code),
CONSTRAINT level_must_be_one_digit CHECK (level BETWEEN 0 AND 9),
CONSTRAINT code_must_be_three_digits CHECK (code BETWEEN 0 AND 999),
CONSTRAINT value_must_be_four_digits CHECK (next_value BETWEEN 0 AND 9999)
);
INSERT INTO sequence_numbers(level,code) VALUES (2,777);
CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
UPDATE sequence_numbers
SET next_value = next_value + 1
WHERE level = $1 AND code = $2
RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;
dan om een ID te krijgen:
INSERT INTO myTable (sequence_number, blah)
VALUES (get_next_seqno(2,777), blah);
Deze benadering betekent dat er maar één transactie tegelijk een rij kan invoegen met een bepaald (niveau, modus) paar tegelijk, maar ik denk dat het racevrij is.
Pas op voor impasses
Er is nog steeds een probleem waarbij twee gelijktijdige transacties kunnen vastlopen als ze proberen rijen in een andere volgorde in te voegen. Er is geen gemakkelijke oplossing hiervoor; je moet ofwel je inserts bestellen zodat je altijd low level en mode invoegt voor high, één insert per transactie doen, of leven met impasses en het opnieuw proberen. Persoonlijk zou ik het laatste doen.
Voorbeeld van het probleem, met twee psql-sessies. Opstelling is:
CREATE TABLE myTable(seq_no integer primary key);
INSERT INTO sequence_numbers VALUES (1,666)
daarna in twee sessies:
SESSION 1 SESSION 2
BEGIN;
BEGIN;
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(2,777));
INSERT INTO myTable(seq_no)
VALUES(get_next_seqno(1,666));
U zult merken dat de tweede insert in sessie 2 blijft hangen zonder terug te keren, omdat deze wacht op een slot dat door sessie 1 wordt vastgehouden. hangen. Er kan geen vooruitgang worden geboekt, dus na een seconde of twee zal PostgreSQL de impasse detecteren en een van de transacties afbreken, zodat de andere verder kan gaan:
ERROR: deadlock detected
DETAIL: Process 16723 waits for ShareLock on transaction 40450; blocked by process 18632.
Process 18632 waits for ShareLock on transaction 40449; blocked by process 16723.
HINT: See server log for query details.
CONTEXT: SQL function "get_next_seqno" statement 1
Uw code moet ofwel voorbereid zijn om dit te verwerken en de hele transactie opnieuw proberen , of het moet de impasse vermijden door transacties met één invoer of zorgvuldige bestelling te gebruiken.
Automatisch niet-bestaande (niveau,code) paren maken
Trouwens, als je (niveau,code) combinaties wilt die nog niet bestaan in de sequence_numbers
tabel die bij het eerste gebruik moet worden gemaakt, is dat verrassend ingewikkeld om goed te krijgen, omdat het een variant is van het upsert-probleem. Ik zou persoonlijk get_next_seqno
. aanpassen om er zo uit te zien:
CREATE OR REPLACE FUNCTION get_next_seqno(level integer, code integer)
RETURNS integer LANGUAGE 'SQL' AS $$
-- add a (level,code) pair if it isn't present.
-- Racey, can fail, so you have to be prepared to retry
INSERT INTO sequence_numbers (level,code)
SELECT $1, $2
WHERE NOT EXISTS (SELECT 1 FROM sequence_numbers WHERE level = $1 AND code = $2);
UPDATE sequence_numbers
SET next_value = next_value + 1
WHERE level = $1 AND code = $2
RETURNING (to_char(level,'FM9')||to_char(code,'FM000')||to_char(next_value,'FM0000'))::integer;
$$;
Deze code kan mislukken, dus je moet altijd voorbereid zijn om transacties opnieuw te proberen. Zoals dat depesz-artikel uitlegt, zijn robuustere benaderingen mogelijk, maar meestal niet de moeite waard. Zoals hierboven beschreven, als twee transacties tegelijkertijd proberen hetzelfde nieuwe (niveau, code) paar toe te voegen, zal er één mislukken met:
ERROR: duplicate key value violates unique constraint "sequence_numbers_pkey"
DETAIL: Key (level, code)=(0, 555) already exists.
CONTEXT: SQL function "get_next_seqno" statement 1