Многоверсионность

Как убедиться в том, что одна и та же строка может существовать в нескольких версиях?

Создадим таблицу с одной строчкой:

=> 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

Конец демонстрации.