TGViewer
Алексей Рыбак: системный дизайн, хайлоад, разработка с агентами Алексей Рыбак: системный дизайн, хайлоад, разработка с агентами @rybakalexey · 10.3K subscribers
Post #210 6.58K
Блокировки при работе с СУБД

Badoo (нынче Bumble, единственный серьезный конкурент Tinder на мировом рынке) когда-то был полноценной социальной сетью. Например, у меня в профайле были сотни фотографий. Лента обновлений была полна всяких событий, по разнообразию совсем не уступающих Фейсбуку, особенно в теперешние времена (кто с кем подружился, что поделал, кого полайкал - с кучей свистоплясок вокруг privacy настроек). И конечно, был мессенжер, и с мессенжером была куча проблем. Мы всё делали на базах, и одна из типичных проблем подобных проектов - битые денормализованные счетчики непрочитанных сообщений. Мы избегали частых операций count и старались все счётчики денормализовать. Бывало, где-то косячили, счетчики начинали биться, а отлаживать большую систему, в которой обновления могут прийти из разных кусков – непросто. Косячили не так чтобы много, и вроде работали достаточно стабильно, но время от времени счетчики всё равно бились. Это не только была наша проблема - хорошо помню минимум два крайне неприятных косяка с битыми счетчиками в Телеграме и мессенжере Фейсбука, на которые напарывался сам как юзер.

Самый простой и прямолинейный способ побороть такие проблемы - пессимистичные блокировки в начале транзакции (select get_lock(), например, и select … for update). Первая транзакция получает блокировку, а вторая транзакция просто не стартует, тк вначале попытается получить блокировку на уже заблокированй ресурс.

А ещё есть метод оптимистичных блокировок через версионирование. Кажется, мы его использовали редко, хотя метод прикольный. Суть в следующем. Куда-то в базе добавляется версия, и обновление идёт не просто по ключу, а по ключу с дополнительным условием, которое гарантирует, что транзакция изменяет “свою” версию. После обновления можно проверить количество обновленных рядов, и если 0 - обработать эту ситуацию как ошибочную.

Однако, вдумчивый читатель мог насторожиться: ведь СУБД бывают разные уровни изоляции. И действительно, в зависимости от уровня изоляции в описанном выше сценарии могут появиться нюансы. Уровень READ UNCOMMITTED рассматривать несерьезно. Если у нас уровень изоляции READ COMMITTED, то оптимистичные блокировки работают, как описано выше: вторая транзакция, которая пытается обновить данные с указанием устаревшей версии, не сможет этого сделать, так как версия уже поменялась. В этом случае никаких ошибок нет, но количество обновленных строк равно 0, и вот эту ситуацию (0 affected rows) нужно поймать и обработать.

Но READ COMMITTED default уже не у всех баз: MySQL, например, в отличие от PostgreSQL, уровень по умолчанию – REPEATABLE READ. И вот если у нас уровень изоляции REPEATABLE READ или даже SERIALIZED, то ситуация может быть совсем другой. На этом уровне движок базы самостоятельно отслеживает конфликтыи лочит данные более агрессивно, и теоретически может быть разное поведение. Например, такое: блокировка до коммита первой транзакции с последующим 0 affected rows. И тогда будет парадокс: я “вижу” старую версию, но не могу её обновить. А ещё транзакции могут быть сложнее, и например для SERIALIZED конфликт разрешится откатом. Поэтому сложные транзакции с оптимистичными блокировками могут работать по-разному для уровней изоляции READ COMMITTED, REPEATABLE READ или SERIALIZED, и это нужно учитывать. Чуть позже напишу пост, как это работает в MySQL и PostgreSQL подробнее.

Не является финансовой рекомендацией ;) Кстати, насчет финансов: именно в биллинге постоянно ловили проблемы с блокировками в MySQL, и там был смешной косяк. Старые версии движка блокировок get_lock() не поддерживали множественные блокировки, но это полбеды. Настоящая беда была в том, что предыдущие блокировки просто сбрасывались молча, что могло приводить к неприятным косякам с деньгами на проде. Мы тогда просили Костю Осипова исправить это поведение, причем я изначально просил Костю не просто пофиксить поведение, но и “пропушить” его в апстрим Оракла, так как самостоятельно поддерживать кастомные сборки базы ну совсем не хотелось. Дело было давно (см. скрин), но с тех пор вроде в MySQL get_clock() работает корректно.
  • 👍 48
  • 🔥 6
More from @rybakalexey
  1. Sep 24, 2026Алексей Рыбак: системный дизайн, хайлоад, разработка с агентами pinned a photo
  2. Sep 24, 2026Devhands — обучение в октябре Раз в месяц мы постим анонсы курсов и митапов, чтобы вы или…
  3. Sep 22, 2026На прошлой неделе я написал пост про “высокомерную иронию”, и всем спасибо, кто проголосов…
  4. Sep 21, 2026Пошаговый план для запуска убыточного B2B SaaS • Найди уникальную идею, не имеющую аналого…
  5. Sep 19, 2026Высокомерная ирония как управленческий инструмент: колхозная токсичность или шоковая терап…
  6. Sep 18, 2026Тусовка, всем привет от Игоря Сысоева! Вы же знаете, кто это такой, а фронтенд это не толь…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →