Мне нужно сохранить уникальный (для каждой строки) номер редакции в таблице document_revisions, где номер редакции ограничен документом, поэтому он не уникален для всей таблицы, только для соответствующего документа.
Я изначально придумал что-то вроде:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Но есть условие гонки!
Я пытаюсь решить это с помощью pg_advisory_lock
, но документации немного, и я не до конца понимаю, и я не хочу что-то блокировать по ошибке.
Допустимо ли следующее, или я делаю это неправильно, или есть лучшее решение?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Не должен ли я вместо этого заблокировать строку документа (key1) для данной операции (key2)? Так что это будет правильное решение:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Может быть, я не привык к PostgreSQL, и SERIAL может быть ограничен, или, может быть, последовательность и nextval()
сделает работу лучше?
источник
Ответы:
Предполагая, что вы сохраняете все редакции документа в таблице, можно было бы не хранить номер редакции, а рассчитать его на основе количества редакций, хранящихся в таблице.
По сути, это производное значение, а не то, что вам нужно хранить.
Функция окна может быть использована для расчета номера ревизии, что-то вроде
и вам понадобится колонка, похожая
change_date
на то, чтобы отслеживать порядок изменений.С другой стороны, если у вас просто есть
revision
свойство документа и оно указывает «сколько раз документ изменялся», то я бы выбрал оптимистический подход к блокировке, что-то вроде:Если это обновляет 0 строк, то произошло промежуточное обновление, и вам необходимо сообщить об этом пользователю.
В общем, постарайтесь сделать ваше решение максимально простым. В этом случае
update
утверждение, а не сselect
последующимinsert
илиupdate
источник
SEQUENCE гарантированно будет уникальным, и ваш сценарий использования будет выглядеть подходящим, если количество ваших документов не слишком велико (иначе вам нужно управлять множеством последовательностей). Используйте предложение RETURNING, чтобы получить значение, сгенерированное последовательностью. Например, используя «A36» в качестве document_id:
Управлять последовательностями нужно будет с осторожностью. Возможно, вы могли бы сохранить отдельную таблицу, содержащую имена документов и связанную с ними последовательность,
document_id
для ссылки при вставке / обновленииdocument_revisions
таблицы.источник
Это часто решается с оптимистической блокировкой:
Если обновление возвращает 0 обновленных строк, вы пропустили обновление, потому что кто-то уже обновил строку.
источник
(Я пришел к этому вопросу, когда пытался заново открыть статью на эту тему. Теперь, когда я нашел ее, я публикую ее здесь на тот случай, если другие будут искать альтернативный вариант для выбранного в данный момент ответа - окно с
row_number()
)У меня такой же вариант использования. Для каждой записи, вставленной в конкретный проект в нашем SaaS, нам нужен уникальный, увеличивающийся номер, который может быть сгенерирован перед лицом одновременных операций
INSERT
и в идеале не имеет пробелов.В этой статье описывается хорошее решение , которое я приведу здесь для простоты и потомства.
document_id
иcounter
.counter
будетDEFAULT 0
вариант, если у вас уже естьdocument
объект , который группирует все версии,counter
могут быть там добавлены.BEFORE INSERT
кdocument_versions
таблице триггер, который атомарно увеличивает счетчик (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
), а затем устанавливаетNEW.version
значение этого счетчика.В качестве альтернативы вы можете использовать CTE для этого на прикладном уровне (хотя я предпочитаю, чтобы он был триггером для согласованности):
В принципе это похоже на то, как вы пытались решить его изначально, за исключением того, что, изменяя строку счетчика в одном операторе, он блокирует чтение устаревшего значения до тех пор, пока не
INSERT
будет зафиксировано.Вот расшифровка стенограммы,
psql
показывающая это в действии:Как вы можете видеть, вы должны быть осторожны
INSERT
с тем, как это происходит, отсюда и версия триггера, которая выглядит так:Это делает
INSERT
s намного более прямым и целостность данных более надежной перед лицомINSERT
s, происходящим из произвольных источников:источник