#Симптом и границы применимости
При повторении транзакции PostgreSQL может вернуть SQLSTATE 40001 serialization_failure или 23505 unique_violation. В отличие от взаимной блокировки это часто связано с повторным применением бизнес-операции либо уровнем изоляции; корректная стратегия учитывает уже выполненные внешние действия.
#Как отличить возможные причины
| Возможная причина | Как узнать | Что делать |
|---|---|---|
| Конфликт сериализацииSERIALIZABLE обнаруживает историю, которую нельзя упорядочить. | SQLSTATE 40001 | Повторять всю транзакцию |
| Повтор платежа/заказаКлиент не уверен, прошёл ли COMMIT, и вставляет дубль. | 23505 при повторе того же запроса | Идемпотентный ключ |
| Неполный повторКод повторяет только последний UPDATE, а не всю логику чтения. | Ошибка снова возникает при прежних вычислениях | Повторить последовательность целиком |
#Пошаговая диагностика и безопасное исправление
- 1. Различите SQLSTATE, а не общий текстСохраните код ошибки и серверный CONTEXT в журнале; 40001 отличается от 40P01 и 23505. Для 40001 PostgreSQL ожидает повтор всей транзакции, для уникальности — анализ ключа.
- 2. Проверьте выставленный уровень изоляцииВ psql из тестового соединения прочитайте настройку по умолчанию, отдельно проверьте явное BEGIN ISOLATION LEVEL в коде. Не снижайте SERIALIZABLE до READ COMMITTED лишь ради исчезновения ошибки.
- psql; чтениеSQLКомандаpsql; чтение. Текущий уровень внутри активной транзакции может быть установлен приложением отдельно.
SHOW default_transaction_isolation;
- 3. Выделите полную транзакцию в приложенииОтметьте в коде начало транзакции, все SELECT, вычисления, запись, COMMIT и внешние вызовы. Повтор с середины способен использовать устаревшие данные, а повтор почты или платежа создать дубли.
- 4. Добавьте ограниченное повторение 40001На тестовой среде разработчик должен перехватывать именно SQLSTATE 40001, выполнять ROLLBACK, создавать новую транзакцию и повторять все чтения и записи с ограничением числа попыток и случайной задержкой. Бесконечный цикл запрещён.
- 5. Защитите бизнес-операцию от повторной доставкиДля заказов/платежей используйте постоянный идемпотентный ключ и UNIQUE в схеме, а внешние побочные действия выполняйте через согласованный механизм подтверждения/очереди. Не передавайте внешний платёж второй раз после спорного COMMIT.
- 6. Испытайте параллельное выполнениеНа копии базы запустите два конкурирующих тестовых запроса к одной бизнес-сущности; зафиксируйте попытки и число созданных строк. Успех — итог соответствует одному бизнес-действию, даже если одна попытка получила 40001.
- 7. Отделите непредвиденные ошибки23505 после повторного запроса с тем же ключом может означать уже успешный первый запрос: запросите существующую запись и сравните параметры. Все прочие SQLSTATE передавайте как ошибки, а не подавляйте общим except.
#Как проверить, что проблема устранена
После конкурентного запуска транзакции либо успешно завершены по правилам, либо безопасно повторены; один бизнес-запрос не производит два заказа или платежа.
#Если исправление не помогло
Если конфликт сохраняется после нескольких попыток, исследуйте порядок чтения/блокировок и долгие транзакции. Не расширяйте число повторов без анализа нагрузки.