В PostgreSQL мне приходится получать идентификаторы небольшими партиями и удалять их одновременно, чтобы два клиента не получали одинаковые идентификаторы.
К сожалению, DELETE
не поддерживает LIMIT
, поэтому я придумал такой запрос:
DELETE FROM codes
WHERE id IN (SELECT id FROM codes WHERE product = 'abc' LIMIT 100 FOR UPDATE)
RETURNING id
К сожалению, это медленный и своего рода последовательный алгоритм (при определенных обстоятельствах) - вы не можете SELECT
пока DELETE
работает, не выбирать один и тот же идентификатор два раза. И ты не сможешь DELETE
, пока не получишь SELECT
результат.
Что приводит к плохой производительности, когда параллельно заходит много клиентов. Каковы мои лучшие варианты распараллеливания/ускорения всего этого?
PS: когда продукты у разных клиентов отличаются - база данных хорошо его распараллеливает, но когда они одинаковы - время ответа начинает линейно расти с количеством запросов параллельных клиентов.
У вас есть индекс на (product, id)
?
Индекс @Charlieface увеличивает время вставки в 3 раза (300%) и дает прирост производительности только на 5%, поэтому он дает гораздо больше недостатков. Проблема не в том, что выбор медленный, выбор сам по себе является асе. и удалить тоже неплохо. в одиночку. проблема начинается, когда они становятся зависимыми друг от друга
Будьте осторожны: распараллеливание звучит хорошо, но только тогда, когда у вас достаточно (виртуальных) аппаратных ресурсов. В основном диск из-за необходимости ввода-вывода. Когда несколько процессов начинают бороться за ресурсы, ваша система может работать даже медленнее, чем при использовании только одного процесса.
Вам придется предоставить нам план EXPLAIN
, потому что то, что вы видите, не то, что я ожидал после добавления индекса.
Предполагая, что вас не волнует порядок использования кодов, на что указывает отсутствие ORDER BY
в вашем запросе.
Используйте SKIP LOCKED, чтобы разрешить распараллеливание. В противном случае регулярно накапливается несколько одновременных вызовов, ожидающих завершения предыдущего(их).
Кроме того, материализуйте свой выбор в CTE, поскольку предложение LIMIT
плохо взаимодействует с предложением блокировки. Видеть:
WITH sel AS MATERIALIZED (
SELECT id
FROM codes
WHERE product = 'abc'
LIMIT 100
FOR UPDATE SKIP LOCKED
)
DELETE FROM codes c
USING sel s
WHERE c.id = s.id
RETURNING c.id;
Ключевое слово MATERIALIZED
не является строго необходимым, так как этот CTE в любом случае не будет встроен. Но для ясности это не помешает.
Теперь вы можете запускать несколько экземпляров этой команды, каждый в отдельном сеансе, одновременно и безопасно.
Чтобы убедиться, что все строки обработаны, проверьте это после завершения обработки. Нравиться:
SELECT EXISTS (SELECT FROM codes WHERE product = 'abc');
Если еще остались строки, выполните последнюю команду без SKIP LOCKED
. Чтобы быть абсолютно уверенным, повторите последние два шага. Или просто зациклите команду без SKIP LOCKED
.
Индекс codes(product)
должен помочь, если фильтр WHERE product = 'abc'
на самом деле является выборочным, т. е. только очень небольшой процент от общего числа строк. В противном случае обычно это требует больше затрат, чем помощи, поскольку индекс увеличивает стоимость записи.
Возможно, вам поможет этот вопрос: stackoverflow.com/questions/5170546/… Это не совсем тот же вариант использования, но в любом случае может помочь.