PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Изчислете следващия първичен ключ - на определен формат

Това изглежда като вариант на проблема с последователността без интервали; също се вижда тук.

Последователностите без пропуски имат сериозни проблеми с производителността и едновременността.

Помислете много добре какво ще се случи, когато се случат няколко вмъквания наведнъж. Трябва да сте готови да опитате отново неуспешни вмъквания или LOCK TABLE myTable IN EXCLUSIVE MODE преди INSERT така че само един INSERT може да бъде в полет наведнъж.

Използвайте таблица с последователности със заключване на редове

Това, което бих направил в тази ситуация е:

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;
$$;

след това, за да получите ID:

INSERT INTO myTable (sequence_number, blah)
VALUES (get_next_seqno(2,777), blah);

Този подход означава, че само една транзакция може да вмъква ред с дадена двойка (ниво, режим) наведнъж, но мисля, че е без състезание.

Пазете се от блокиране

Все още има проблем, при който две едновременни транзакции могат да блокират, ако се опитат да вмъкнат редове в различен ред. Няма лесно решение за това; трябва или да поръчате своите вмъквания, така че винаги да вмъквате ниско ниво и режим преди високо, да правите едно вмъкване на транзакция или да живеете със застой и да опитате отново. Лично аз бих направил второто.

Пример за проблема с две psql сесии. Настройката е:

CREATE TABLE myTable(seq_no integer primary key);
INSERT INTO sequence_numbers VALUES (1,666)

след това в две сесии:

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));

Ще забележите, че второто вмъкване в сесия 2 ще виси, без да се връща, защото изчаква заключване, задържано от сесия 1. Когато сесия 1 продължи, за да се опита да получи заключване, задържано от сесия 2 във второто вмъкване, тя също ще виси. Не може да се постигне напредък, така че след секунда или две PostgreSQL ще открие блокирането и ще прекрати една от транзакциите, позволявайки на другата да продължи:

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

Вашият код трябва или да е подготвен да се справи с това и да опита отново цялата транзакция , или трябва да избягва блокирането с помощта на транзакции с едно вмъкване или внимателно подреждане.

Автоматично създаване на несъществуващи двойки (ниво, код)

BTW, ако искате (ниво, код) комбинации, които все още не съществуват в sequence_numbers таблица, която трябва да бъде създадена при първа употреба, това е изненадващо сложно да се оправи, тъй като е вариант на проблема с upsert. Аз лично бих променил get_next_seqno да изглежда така:

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;

$$;

Този код може да се провали, така че винаги трябва да сте готови да опитате повторно транзакции. Както се обяснява в тази статия на depesz, по-стабилни подходи са възможни, но обикновено не си струва. Както е написано по-горе, ако две транзакции едновременно се опитат да добавят една и съща нова двойка (ниво, код), едната ще се провали с:

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


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Комбинирайте две таблици в нова, така че избраните редове от другата да бъдат игнорирани

  2. грешка при инсталиране на python pip psycopg2

  3. Съставният ПЪРВИЧЕН КЛЮЧ налага ограничения NOT NULL върху участващите колони

  4. Моите любими разширения на PostgreSQL - втора част

  5. Вземете името на деня от дата в PostgreSQL