Важно только, когда это касается меня
— каждый #CowboyDeveloper когда-либо

Уровни изоляции
Вы знаете, что уровень изоляции по умолчанию в Postgres — «Read Committed», верно?
Знаете? Что ж, хорошо для тебя, Гений, потому что я, черт возьми, этого не делал.
Но сначала небольшое отступление.

Уровни изоляции в базе данных относятся к различным способам, которыми параллельные транзакции могут влиять друг на друга. Скажем, я читаю кучу данных из базы данных и базы данных, и при этом вы обновляете одну из записей, что происходит?
• Вижу ли я данные до вашего обновления?
• Вижу ли я данные после вашего обновления?
• Если мое чтение на самом деле представляет собой пару просмотров таблицы внутри одной транзакции, что произойдет? Получу ли я обе версию данных до и после?
• Мое чтение просто bork с ошибкой кто-то обновляет эту таблицу?
• Получу ли я NullPointerException? (Извините. Плохой юмор Java. Мой плохой)

Благодаря людям из ANSI и, что более важно, Атул Адья, есть довольно хорошие — и строгие! — определения того, чем на самом деле являются эти уровни изоляции и как они соотносятся с существующими стандартами. Что еще более важно для меня, Postgres систематизировал это с версии 9.1 и позволяет вам определить, с каким уровнем изоляции вы хотите работать.

И это возвращает меня к сегодняшнему дню. На (чрезмерно! Очень чрезмерно!) упрощенном уровне Postgres допускает три уровня изоляции.

  1. Подтвержденное чтение:
    При каждом чтении в рамках транзакции отображается согласованное представление базы данных. Однако, если транзакция имеет несколько прочтений, каждое из этих прочтений может видеть разные представления базы данных!
  2. Повторяющееся чтение:
    Каждая транзакция видит согласованное представление базы данных (включая все операции чтения внутри транзакции).
  3. Сериализуемый:
    Параллельные транзакции могут выполняться только в том случае, если система может доказать, что если они выполняются в определенном порядке, они всегда иметь тот же результат. Или, другими словами, как будто все транзакции происходят одна за другой.

По умолчанию используется «Чтение подтверждено». Помните об этом, это важно.

Абстракции
Давным-давно, когда динозавры бродили по земле, а SRE назывались «сисадминами» — да, это еще до DevOps! — раньше людям было насрать на то, что на самом деле делает база данных. На самом деле им приходилось обращать внимание на такие вещи, как «операторы SQL» и «внешние ключи» и даже «индексы»!
А если серьезно, близость к базе данных означала, что вы должны были на самом деле знать, что там происходит — что делают операторы sql, как влияет изменение транзакций, добавление индекса , и так далее.
В настоящее время, благодаря абстракциям, предоставляемым средами MVC, на которые вы в основном полагаетесь (Django, Rails и т. д.), вы в значительной степени склонны игнорировать сами базы данных. В лучшем случае вы входите и добавляете индекс, потому что «работа стала медленной».

Дырявые абстракции
Вот где жизнь становится интереснее. Помните место, где уровень изоляции по умолчанию в Postgres — «Read Committed»? Это означало, что одна транзакция могла видеть разные представления базы данных при каждом сканировании таблицы?

Что ж, хорошая новость заключается в том, что это имеет тенденцию — вроде как — работать для большинства наших вариантов использования. В конце концов, большая часть того, что мы делаем с MVC, приводит к довольно простому «однократному» доступу к базе данных. Более того, при разумном использовании пулов соединений и «CQRS-lite» (запись идет на этот синглтон, чтение идет на эти соединения), мы в основном избегаем любых вопросы.

Однако в основном. Потому что, если ваша система остается в рабочем состоянии достаточно долго, рост сложности в конечном итоге приводит к тому, что вы начинаете наблюдать странное поведение, когда обновление адреса здесь приводит к исчезновению элемента инвентаря через там, и как, черт возьми, адреса и предметы инвентаря влияют друг на друга, я даже не могу…

Что еще хуже, это то, что влияет на вас только под нагрузкой, и даже тогда это элемент, связанный с параллельным доступом, что означает, что вам нужна какая-то структура трассировки (привет, OpenTracing!), что много значит крови, пота и слез, пока не поймешь, что это глубоко внутри Postgres.

Изменить настройки по умолчанию?
Итак, это легко исправить, верно? Просто измените уровень изоляции по умолчанию на Serializable, и все готово, верно?
Ну, возможно. Дело в том, что Read Committed работает быстрее, иногда намного быстрее, потому что многие проверки Serializable выполняются с помощью мониторинга транзакций, проверок предикатов, и тому подобное (подумайте о «много накладных расходов»). Более того, транзакции будут счастливо прерываться из-за возможных ошибок, даже если фактическая ошибка не существует (например, ограничение ключа, даже если фактический ключ не настоящее время).

Тем не менее, существует хороший путь — разработка с Serializable и запуск в производство с Read Committed. Принудительные ограничения, которые есть в вашем коде, будут применять целый набор лучших практик.

Имейте в виду, что это вызывает несколько проблем
• Разработка и производство теперь различаются. С другой стороны, обычно это так...
• Если кто-то разрабатывает с Read Committed, значит, у вас снова проблема. Здесь может помочь тестирование…
• Я уверен, что есть пограничные случаи, которые вас укусят, а меня еще не укусили. Опять же, всегда есть крайние случаи…

(Эта статья также есть в моем блоге)