
СНИМКИ ДАННЫХ
~~~~~~~~~~~~~

Смоделируем ситуацию, воспользовавшись уровнем изоляции repeatable read:

первая транзакция: |--------------------------------
просто транзакция:       |--- insert ---|
вторая транзакция:                            |-----

        => create table t(s text);
        CREATE TABLE

        => insert into t values ('first');
        INSERT 0 1

        1| => begin;
        1| BEGIN

        1| => set transaction isolation level repeatable read;
        1| SET

.......................................................................

Вот что видит первая транзакция:

        1| => select * from t;
        1|    s   
        1| -------
        1|  first
        1| (1 row)
        1| 

.......................................................................

        => begin;
        BEGIN

        => insert into t values ('second');
        INSERT 0 1

Запомним номер "просто транзакции":

        => select txid_current();
         txid_current 
        --------------
               554679
        (1 row)
        

        => commit;
        COMMIT

.......................................................................

        2| => begin;
        2| BEGIN

        2| => set transaction isolation level repeatable read;
        2| SET

.......................................................................

Вот что видит вторая транзакция:

        2| => select * from t;
        2|    s    
        2| --------
        2|  first
        2|  second
        2| (2 rows)
        2| 

А что сейчас увидит первая?

.......................................................................

То же самое, что и в первый раз - в этом и есть смысл уровня repeatable read.

        1| => select * from t;
        1|    s   
        1| -------
        1|  first
        1| (1 row)
        1| 

.......................................................................

Все разница в видимости определяется снимками. Посмотрим на снимки:

        1| => select txid_current_snapshot();
        1|  txid_current_snapshot 
        1| -----------------------
        1|  554679:554679:
        1| (1 row)
        1| 

        2| => select txid_current_snapshot();
        2|  txid_current_snapshot 
        2| -----------------------
        2|  554680:554680:
        2| (1 row)
        2| 

Снимок отображается как три значения, разделенные двоеточиями:
* Первое значение - минимальный номер активной транзакции на момент создания снимка.
  Любая более ранняя транзакция будет либо зафиксирована и видна,
  либо откачена и не видна.
* Второе значение - максимальный (еще пока не выданный) номер транзакции.
  Все транзакции начиная с этого номера не будут видны в снимке.
* Третье значение - список номеров активных транзакций на момент создания снимка.

Почему вторая транзакция не видит первую в качестве активной?

.......................................................................

Транзакция не получает номер до тех пор, пока не начнет изменять данные.
До этого она обходится виртуальным номером (virtualxid). Например, первая:

        1| => select virtualxid, mode
        1| => from pg_locks
        1| => where pid = pg_backend_pid() and locktype = 'virtualxid';
        1|  virtualxid |     mode      
        1| ------------+---------------
        1|  3/16       | ExclusiveLock
        1| (1 row)
        1| 

Виртуальный номер состоит из двух частей: номера серверного процесса
и уникального номера в пределах этого процесса.

.......................................................................

Если же попробовать посмотреть номер транзакции с помощью функции txid_current,
он будет выделен:

        1| => select txid_current();
        1|  txid_current 
        1| --------------
        1|        554680
        1| (1 row)
        1| 

.......................................................................

Сопоставим снимки и информацию о версионности в строках:

        1| => select txid_current_snapshot();
        1|  txid_current_snapshot 
        1| -----------------------
        1|  554679:554679:
        1| (1 row)
        1| 

        2| => select txid_current_snapshot();
        2|  txid_current_snapshot 
        2| -----------------------
        2|  554680:554680:
        2| (1 row)
        2| 

        => select xmin, xmax, * from t;
          xmin  | xmax |   s    
        --------+------+--------
         554678 |    0 | first
         554679 |    0 | second
        (2 rows)
        

.......................................................................

Первая транзакция видит первую строку, так как xmin меньше максимального номера,
транзакция с номером xmin не входит в активные и зафиксирована.
Но не видит вторую строку, так как xmin строки больше или равен максимальному
номеру снимка.

Вторая транзакция видит обе строки, так как xmin меньше максимального номера,
транзакция с номером xmin не входит в активные и зафиксирована.

.......................................................................

Рассмотрим еще один пример. Здесь у первой транзакции будет настоящий номер.

первая транзакция: |--- insert ---------------------
просто транзакция:       |--- insert ---|
вторая транзакция:                            |-----

        2| => commit;
        2| COMMIT

        1| => commit;
        1| COMMIT

        => truncate table t;
        TRUNCATE TABLE

.......................................................................

        1| => begin;
        1| BEGIN

        1| => set transaction isolation level repeatable read;
        1| SET

        1| => insert into t values ('first');
        1| INSERT 0 1

        1| => select * from t;
        1|    s   
        1| -------
        1|  first
        1| (1 row)
        1| 

.......................................................................

        => insert into t values ('second');
        INSERT 0 1

.......................................................................

        2| => begin;
        2| BEGIN

        2| => set transaction isolation level repeatable read;
        2| SET

        2| => select * from t;
        2|    s    
        2| --------
        2|  second
        2| (1 row)
        2| 

.......................................................................

Снова смотрим на снимки. Сначала первая транзакция:

        1| => select txid_current_snapshot();
        1|  txid_current_snapshot 
        1| -----------------------
        1|  554682:554682:
        1| (1 row)
        1| 

Информацию о версионности придется смотреть из двух транзакций, так как ни одна из них
не видит полной картины (или можно было бы воспользоваться pageinspect):

        1| => select xmin, xmax, * from t;
        1|   xmin  | xmax |   s   
        1| --------+------+-------
        1|  554682 |    0 | first
        1| (1 row)
        1| 

        2| => select xmin, xmax, * from t;
        2|   xmin  | xmax |   s    
        2| --------+------+--------
        2|  554683 |    0 | second
        2| (1 row)
        2| 

.......................................................................

Первая транзакция видит первую строку, так как она ее создала.
Но не видит вторую строку, так как xmin строки больше или равен максимальному
номеру снимка.

.......................................................................

Теперь вторая транзакция:

        2| => select txid_current_snapshot();
        2|  txid_current_snapshot 
        2| -----------------------
        2|  554682:554684:554682
        2| (1 row)
        2| 

        1| => select xmin, xmax, * from t;
        1|   xmin  | xmax |   s   
        1| --------+------+-------
        1|  554682 |    0 | first
        1| (1 row)
        1| 

        2| => select xmin, xmax, * from t;
        2|   xmin  | xmax |   s    
        2| --------+------+--------
        2|  554683 |    0 | second
        2| (1 row)
        2| 

.......................................................................

Вторая транзакция не видит первую строку, так как транзакция с номером xmin
была активной на момент создания снимка.
Но видит вторую строку, так как xmin меньше максимального номера,
транзакция с номером xmin не входит в активные и зафиксирована.

.......................................................................

БЛОКИРОВКИ НА УРОВНЕ СТРОК
~~~~~~~~~~~~~~~~~~~~~~~~~~

Рассмотрим пример двух транзакций, обновляющих одну строку.

        1| => commit;
        1| COMMIT

        2| => commit;
        2| COMMIT

        => create table block(n numeric);
        CREATE TABLE

        => insert into block values (42);
        INSERT 0 1

.......................................................................

        1| => begin;
        1| BEGIN

        1| => update block set n = 1;
        1| UPDATE 1

        1| => select txid_current();
        1|  txid_current 
        1| --------------
        1|        554686
        1| (1 row)
        1| 

        2| => begin;
        2| BEGIN

        2| => update block set n = 2;

.......................................................................

Вторая транзакция заблокирована до тех пор, пока первая не завершит транзакцию
либо фиксацией изменений, либо откатом.
О том, что строка изменяется, транзакция узнает по тому, что xmax
принадлежит активной транзакции.

        => select xmin, xmax from block;
          xmin  |  xmax  
        --------+--------
         554685 | 554686
        (1 row)
        

.......................................................................

Расширение pgrowlocks позволяет удобно посмотреть на блокировку:

        => create extension if not exists pgrowlocks;
        CREATE EXTENSION

        => select * from pgrowlocks('block');
         locked_row | locker | multi |   xids   |       modes       |  pids   
        ------------+--------+-------+----------+-------------------+---------
         (0,1)      | 554686 | f     | {554686} | {"No Key Update"} | {11591}
        (1 row)
        

Здесь мы видим тип блокировки и номер процесса.

.......................................................................

Кроме блокировки строки тот факт, что одна транзакция заблокирована другой,
отмечается и в блокировке высокого уровня:

        1| => select transactionid, pid, mode, granted
        1| => from pg_locks
        1| => where locktype = 'transactionid';
        1|  transactionid |  pid  |     mode      | granted 
        1| ---------------+-------+---------------+---------
        1|         554687 | 11604 | ExclusiveLock | t
        1|         554686 | 11591 | ExclusiveLock | t
        1|         554686 | 11604 | ShareLock     | f
        1| (3 rows)
        1| 

.......................................................................

Существуют как эксклюзивные блокировки строки (for update, for no key update),
так и разделяемые (for key share, for share).
В случае разделяемой блокировки одна строка может быть заблокирована несколькими
транзакциями одновременно. Посмотрим пример.

        1| => rollback;
        1| ROLLBACK

        2| => rollback;
        2| UPDATE 1
        2| ROLLBACK

.......................................................................

        1| => begin;
        1| BEGIN

        1| => select * from block for share;
        1|  n  
        1| ----
        1|  42
        1| (1 row)
        1| 

        2| => begin;
        2| BEGIN

        2| => select * from block for share;
        2|  n  
        2| ----
        2|  42
        2| (1 row)
        2| 

.......................................................................

        => select * from pgrowlocks('block');
         locked_row | locker | multi |      xids       |     modes     |     pids      
        ------------+--------+-------+-----------------+---------------+---------------
         (0,1)      |      5 | t     | {554689,554690} | {Share,Share} | {11591,11604}
        (1 row)
        

Что при этом записано в поле xmax строки?

.......................................................................

В поле xmax записывается число, называемое multixact.
Специальный бит указывает, что это число не является номером транзакции.
Расшифровка, которую показывает pgrowlocks, хранится в каталоге $PGDATA/pg_multixact.

        => select xmin, xmax, * from block;
          xmin  | xmax | n  
        --------+------+----
         554685 |    5 | 42
        (1 row)
        

.......................................................................

БЛОКИРОВКИ НА УРОВНЕ ТАБЛИЦ
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Кроме блокировок на уровне строк, существуют также блокировки на уровне таблиц.
Всего в PostgreSQL есть восемь различных режимов таких блокировок.
Не вдаваясь в детали, рассмотрим простой пример.

        1| => rollback;
        1| ROLLBACK

        2| => rollback;
        2| ROLLBACK

.......................................................................

        1| => begin;
        1| BEGIN

        1| => select * from block;
        1|  n  
        1| ----
        1|  42
        1| (1 row)
        1| 

        1| => select relation::regclass, pid, mode, granted
        1| => from pg_locks
        1| => where locktype = 'relation' and relation != 'pg_locks'::regclass;
        1|  relation |  pid  |      mode       | granted 
        1| ----------+-------+-----------------+---------
        1|  block    | 11591 | AccessShareLock | t
        1| (1 row)
        1| 

При обращении к таблице она блокируется в разделяемом режиме AccessShareLock,
чтобы другой процесс не мог изменить определение этой таблицы (например,
удалить столбец).

.......................................................................

        1| => update block set n = 1;
        1| UPDATE 1

        1| => select relation::regclass, pid, mode, granted
        1| => from pg_locks
        1| => where locktype = 'relation' and relation != 'pg_locks'::regclass;
        1|  relation |  pid  |       mode       | granted 
        1| ----------+-------+------------------+---------
        1|  block    | 11591 | AccessShareLock  | t
        1|  block    | 11591 | RowExclusiveLock | t
        1| (2 rows)
        1| 

При изменении таблицы добавляется блокировка в режиме RowExclusiveLock,
запрещающая, в частности, выполнение команды create index по таблице.

.......................................................................

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

        1| => \q
        2| => \q
        => \q
