flapenguin.me

Почему ON CONFLICT DO NOTHING тебя ненавидит и конфликтует

|

Оригинал https://t.me/bits_and_bicycles/22

В стандарте SQL есть такая конструкция INSERT ... ON CONFLICT (constraint) DO UPDATE, также известная как UPSERT. Нужна чтобы добавить или подобновить строку. на И есть её брат-близнец INSERT ... ON CONFLICT (constraint) DO NOTHING, которая просто превращается в no-op, если строка уже есть.

Но DO NOTHING этот работает не совсем интуитивно. По крайней мере, в #postgresql.

Представь, что у тебя есть CREATE TABLE numbers (value INT UNIQUE) и ты выполняешь одновременно два одинаковых запроса в разных сессиях (горизонтальное масштабирование и вот это вот всё): INSERT INTO numbers (value) VALUES (1) ON CONFLICT DO NOTHING.

Одной сессии повезёт больше и она добавит строку, пусть даже в транзакции. Другой сессии же придётся встать в очередь и ждать. Потому что любое предположение о другой транзакции может быть неправильным: строку могут удалить следующим запросом, да всю транзакцию могут отменить, в конце концов.

И вот ты вроде написал ON CONFLICT DO NOTHING, а де-факто в PostgreSQL получил не DO NOTHING, а DO NOTHING NOTHING OR LOCK numbers_value_unique_ix. Насколько долгий lock? На lock_timeout, который ты, конечно же, забыл настроить для своей сессии.

“Ну и что” – скажешь ты, “мы хотели одну строку и получили в итоге одну строку, пусть и подождали немного”. И, действительно, обычно нет ничего страшного если строка одна. Но если ты UPSERT’ишь с сотню одинаковых строк в нескольких сессиях, то конфликтов становится слишком много и ты рискуешь выстроить все свои INSERT’ы в очередь, которая просто не будет успевать разгребаться.