Есть ли способ избежать вызова nextval (), если в PostgreSQL происходит сбой вставки? - PullRequest
6 голосов
/ 10 января 2010

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

  CREATE TABLE users (
    id      INTEGER PRIMARY KEY DEFAULT nextval('groups_id_seq'::regclass),
    name    VARCHAR(255) UNIQUE NOT NULL
  );

  INSERT users (name) VALUES ('foo');
  INSERT users (name) VALUES ('foo');
  INSERT users (name) VALUES ('bar');

Вторая вставка завершается неудачно, но последовательность groups_id_seq уже увеличена, поэтому, когда добавляется 'bar', это оставляет пробел в идентификационных номерах.

Есть ли способ сообщить PostgreSQL, чтобы он выбирал следующее значение, только если соблюдаются другие ограничения, или я должен сначала проверить, используя SELECT, если имя не дублируется? Это все еще не гарантирует отсутствие пробелов, но, по крайней мере, уменьшит их количество до тех редких случаев, когда другой процесс пытается вставить одно и то же имя одновременно

Ответы [ 3 ]

12 голосов
/ 10 января 2010

Я так не думаю: основная особенность последовательностей состоит в том, что возможны пропуски (представьте себе две параллельные транзакции, причем одна выполняет ROLLBACK). Вы должны игнорировать пробелы. Почему они проблема в вашем случае?

6 голосов
/ 10 января 2010

Если вам нужны последовательности без промежутков - есть способы сделать это, но это не тривиально и, безусловно, намного медленнее.

Также - если вы беспокоитесь о «использовании слишком большого количества идентификаторов» - просто определите id как bigserial.

5 голосов
/ 10 января 2010

Возможно, хотя и громоздко, сделать это. Как говорит bortzmeyer , опасно полагаться на смежные значения последовательностей, поэтому лучше просто оставить все как есть.

Если вы не можете:

Каждый доступ к таблице, который может привести к тому, что строка будет иметь определенное имя (то есть, каждое INSERT к этой таблице, и если вы разрешите это (хотя это плохая практика) каждые UPDATE, что может изменить name поле) должен сделать это внутри транзакции, которая сначала блокирует что-либо . Самый простой и наименее производительный вариант - просто заблокировать всю таблицу, используя LOCK users IN EXCLUSIVE MODE (добавление последних 3 слов разрешает одновременный доступ для чтения другим процессам, что безопасно).

Однако это очень грубая блокировка, которая снижает производительность при наличии множества одновременных модификаций users; лучшим вариантом будет блокировка одной соответствующей строки в другой таблице, которая уже должна существовать. Этот ряд можно заблокировать с помощью SELECT ... FOR UPDATE. Это имеет смысл только при работе с «дочерней» таблицей, которая имеет зависимость FK от другой «родительской» таблицы.

Например, представьте себе на время, что мы на самом деле пытаемся безопасно создать новый orders для customer, и что эти ордера каким-то образом имеют идентифицирующие «имена». (Я знаю, плохой пример ...) orders имеет зависимость FK от customers. Затем, чтобы предотвратить создание двух заказов с одинаковым именем для данного клиента, вы можете сделать следующее:

BEGIN;

-- Customer 'jbloggs' must exist for this to work.  
SELECT 1 FROM customers
WHERE id = 'jbloggs'
FOR UPDATE

-- Provided every attempt to create an order performs the above step first,
-- at this point, we will have exclusive access to all orders for jbloggs.
SELECT 1 FROM orders
WHERE id = 'jbloggs'
AND order_name = 'foo'

-- Determine if the preceding query returned a row or not.
-- If it did not:
INSERT orders (id, name) VALUES ('jbloggs', 'foo');

-- Regardless, end the transaction:
END;

Обратите внимание, что не достаточно просто заблокировать соответствующую строку в users с помощью SELECT ... FOR UPDATE - если строка еще не существует, несколько одновременных процессов могут одновременно сообщить, что строка не существуют, а затем пытаются выполнить одновременные вставки, что приводит к неудачным транзакциям и, таким образом, к пробелам в последовательности.

Любая схема блокировки будет работать; важно то, что любой, кто пытается создать строку с тем же именем, должен попытаться заблокировать один и тот же объект .

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