Как убедиться в том, что одна и та же строка может существовать в нескольких версиях?
Создадим таблицу с одной строчкой:
=> CREATE TABLE t(s text);
CREATE TABLE
=> INSERT INTO t VALUES ('Первая версия');
INSERT 0 1
Начнем транзакцию и выведем ее номер:
=> BEGIN;
BEGIN
=> SELECT txid_current();
txid_current -------------- 559 (1 row)
Транзакция видит первую (и пока единственную) версию строки:
=> SELECT *, xmin, xmax FROM t;
s | xmin | xmax ---------------+------+------ Первая версия | 558 | 0 (1 row)
Здесь мы дополнительно показываем номера транзакций, ограничивающих видимость данной версии строки. Строка создана предыдущей транзакцией, а xmax=0 означает, что эта версия актуальна.
Теперь начнем другую транзакцию в другом сеансе:
=> BEGIN;
BEGIN
=> SELECT txid_current();
txid_current -------------- 560 (1 row)
Транзакция видит ту же версию:
=> SELECT *, xmin, xmax FROM t;
s | xmin | xmax ---------------+------+------ Первая версия | 558 | 0 (1 row)
Теперь изменим строку во второй транзакции.
=> UPDATE t SET s = 'Вторая версия';
UPDATE 1
Вот что получилось:
=> SELECT *, xmin, xmax FROM t;
s | xmin | xmax ---------------+------+------ Вторая версия | 560 | 0 (1 row)
А что увидит первая транзакция?
Поскольку изменение не зафиксировано, первая транзакция продолжает видеть первую версию строки:
=> SELECT *, xmin, xmax FROM t;
s | xmin | xmax ---------------+------+------ Первая версия | 558 | 560 (1 row)
Обратите внимание на xmax - значение показывает, что в настоящий момент другая транзакция меняет строку. Вообще говоря, такое "подглядывание" нарушает изоляцию, поэтому поля xmin и xmax скрытые и в реальной работе их использовать не стоит.
Теперь зафиксируем изменения.
=> COMMIT;
COMMIT
Что теперь увидит первая транзакция?
Теперь и первая транзакция видит вторую версию строки:
=> SELECT *, xmin, xmax FROM t;
s | xmin | xmax ---------------+------+------ Вторая версия | 560 | 0 (1 row)
=> COMMIT;
COMMIT
Первая версия строки больше не видна ни в одной транзакции и может быть удалена процессом очистки.
Повторим наш опыт, но теперь пусть обе транзакции попытаются изменить одну и ту же строку.
=> BEGIN;
BEGIN
=> UPDATE t SET s = 'Третья версия';
UPDATE 1
И во второй транзакции:
=> BEGIN;
BEGIN
=> UPDATE t SET s = 'Четвертая версия';
Вторая транзакция "повисла": она не может изменить строку, пока первая транзакция не снимет блокировку.
=> COMMIT;
COMMIT
Теперь вторая транзакция может продолжить выполнение:
UPDATE 1
=> COMMIT;
COMMIT
Конец демонстрации.