Могу ли я доверять ручную последовательность целочисленного поля в Postgres, если она выполняется внутри транзакции? - PullRequest
0 голосов
/ 22 марта 2012

У меня есть таблица с именем ticket, и в ней есть поле с именем number и внешний ключ с именем client, который должен работать почти как автоматическое поле (с увеличением на 1 для каждой новой записи), кроме что клиентская цепочка должна уметь указывать начальный номер. Это не уникальное поле, потому что несколько клиентов, несомненно, будут использовать одни и те же номера (например, начиная с 1001). В моем приложении я выбираю строку с самым высоким значением number и использую это значение number + 1 для создания следующей записи number. Все это происходит внутри одной транзакции (выборка и сохранение новой записи). Правда ли, что мне не придется беспокоиться о том, что ticket когда-либо получит неправильный (дубликат) number в ситуации высокой нагрузки, или транзакция защитит от такой возможности? (примечание: я использую PostgreSQL 9.x)

Ответы [ 2 ]

1 голос
/ 23 марта 2012

Вам нужно беспокоиться о дублирующих номерах.

Типичный проблемный сценарий: транзакция T1 читает N и создает новую строку с N + 1. Но до того, как T1 совершит , другая транзакция T2 рассматривает N как максимум для этого клиента и создает еще одну новую строку с конфликтом N + 1 =>.

Есть много способов избежать этого; Вот простой кусок кода plpgsql, который реализует один метод, предполагая уникальный индекс (клиент, номер). Решение состоит в том, чтобы обеспечить одновременное выполнение вставок, но в случае нарушения уникального индекса повторите попытку с обновленными значениями, пока они не будут приняты (хотя это не занятый цикл, поскольку одновременные вставки блокируются до фиксации других транзакций)

do
$$
begin
 loop
  BEGIN
    -- client number is assumed to be 1234 for the sake of simplicity
    insert into the_table(client,number)
       select 1234, 1+coalesce(max(number),0) from the_table where client=1234;
    exit; 
  EXCEPTION
   when unique_violation then -- nothing (keep looping)
  END;
 end loop;
end$$;

Этот пример немного похож на реализацию UPSERT из документации PG. Он легко переносится в функцию plpgsql, принимающую идентификатор клиента в качестве ввода.

1 голос
/ 22 марта 2012

без блокировки таблицы целом при каждой вставке / обновлении, нет. То, как транзакции работают в PostgreSQL, означает, что новые строки, появляющиеся в результате одновременных транзакций, никогда не конфликтуют друг с другом; и это именно то, что будет происходить.

Вы должны убедиться, что обновления вызывают конфликт строк. В основном вам нужно реализовать нечто похожее на механику, используемую нативными последовательностями PostgreSQL.

Я бы добавил еще один столбец в таблицу, на которую ссылается ваш столбец client, чтобы представить last_val последовательности, которую вы будете использовать. Таким образом, каждая транзакция будет выглядеть примерно так:

BEGIN;

SET TRANSACTION SERIALIZABLE;

UPDATE clients 
  SET footable_last_val = footable_last_val + 1 
  WHERE clients.id = :client_id;

INSERT INTO footable(somecol, client_id, number) 
  VALUES (:somevalue, 
          :client_id, 
          (SELECT footable_last_val 
             FROM clients 
             WHERE clients.id = :client_id));

COMMIT;

Так что первое обновление таблицы клиентов завершается неудачно из-за конфликта версий до достижения вставки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...