Ответ
В PostgreSQL параллельный доступ к данным реализован через механизм MVCC (Multi-Version Concurrency Control) — управление конкурентным доступом через многоверсионность. Это позволяет нескольким транзакциям читать и изменять данные одновременно, минимизируя блокировки.
Ключевые принципы:
- Снимки данных: Каждая транзакция работает со своим «снимком» (snapshot) данных, который был актуален на момент её начала. Она не видит изменений, сделанных другими транзакциями, которые начались позже.
- Версии строк: Вместо того чтобы изменять данные «на месте», PostgreSQL создает новую версию строки при каждом
UPDATE. Старая версия не удаляется сразу, а помечается как неактуальная.
Как это работает на уровне строк:
Каждая строка в таблице имеет скрытые системные поля:
xmin: ID транзакции, которая создала эту версию строки.xmax: ID транзакции, которая удалила (или обновила) эту версию строки. Изначально равен 0.
Пример жизненного цикла строки:
-- 1. Транзакция с ID=100 вставляет новую строку.
-- Для этой строки xmin=100, xmax=0.
BEGIN; -- txid 100
INSERT INTO users (name) VALUES ('Alice');
COMMIT;
-- 2. Транзакция с ID=200 обновляет эту строку.
-- PostgreSQL выполняет два действия:
-- a) Помечает старую версию строки: xmax=200.
-- b) Создает новую версию строки с данными 'Bob' и xmin=200.
BEGIN; -- txid 200
UPDATE users SET name = 'Bob' WHERE name = 'Alice';
COMMIT;
Очистка старых версий:
«Мертвые» версии строк, которые больше не видны ни одной активной транзакции, удаляются фоновым процессом VACUUM. Это необходимо для освобождения места и предотвращения разрастания таблиц.
Ответ 18+ 🔞
А, слушай, про этот ваш MVCC в Постгресе! Ну, это ж просто гениальная штука, чтобы все не передрались за данные, как последние пидорасы у разбитого корыта.
Вот представь: не надо всем лезть в одну миску с похлёбкой и хватать друг друга за руки. Каждой транзакции — свой личный, блядь, снимок этой похлёбки на момент, когда она села за стол. И она спокойно жрёт, не видя, что сосед уже свою ложку в общую кастрюлю сунул. Красота, ёпта!
А как это технически, спросишь? Ну, тут всё просто, как три копейки. Строки не перетирают друг друга, как последние мудаки. При каждом апдейте рождается новая версия строки, а старая — не сдохла, а просто стала призраком. У каждой версии есть свои тайные метки: xmin — кто её родил, xmax — кто её, сука, убил (или сделал невидимой).
Смотри, как это в жизни:
-- Допустим, транзакция под номером 100 родила строку.
-- У той строки в паспорте: xmin=100 (папаша), xmax=0 (жива, здорова).
BEGIN; -- txid 100
INSERT INTO users (name) VALUES ('Alice');
COMMIT;
-- А потом приходит транзакция 200, вся такая важная, и говорит: "Алиса? Не, не слышали. Теперь будет Боб".
-- И что делает система? Она не стирает Алису нахуй!
-- Она ставит на старой версии штамп "убита транзакцией 200" (xmax=200).
-- А рядом создаёт новенькую, свеженькую версию с именем 'Bob' и паспортом xmin=200.
BEGIN; -- txid 200
UPDATE users SET name = 'Bob' WHERE name = 'Alice';
COMMIT;
И вот эти все призрачные версии, которым уже никто не нужен, болтаются в таблице, как сопли. Если их не убирать — таблица раздуется до овердохуища, и работать будет медленнее, чем пьяный слесарь в понедельник. За этим следит специальный дворник — процесс VACUUM. Он ходит и подметает эти мёртвые версии, которые уже ни одна живая транзакция не видит. Освобождает место, чтобы не было пиздеца.
Вот и вся магия, без всяких глобальных замков на всю таблицу. Умно, блядь!