СНИМКИ ДАННЫХ ~~~~~~~~~~~~~ Смоделируем ситуацию, воспользовавшись уровнем изоляции 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