# Виртуальные и реальные xid _MVCC и видимость · PostgreSQL Knowledge Base_ **TL;DR:** Каждая транзакция сразу получает виртуальный xid - дешёвую пару «бэкенд/счётчик», которая ничего не расходует. Настоящий 32-битный xid выдаётся только когда транзакция впервые что-то пишет. Поэтому read-only транзакции не тратят xid вовсе - это бережёт ограниченное пространство счётчика и отдаляет wraparound. Настоящих идентификаторов транзакций (xid) ограниченное число: счётчик 32-битный, около четырёх миллиардов значений, и он по кругу переиспользуется. Если бы каждая транзакция, включая голые `SELECT`, сжигала по xid, счётчик летел бы вперёд и заморозка с борьбой за wraparound случались бы куда чаще. PostgreSQL экономит. Транзакция получает реальный xid не сразу, а только по необходимости. ## Два идентификатора | Идентификатор | Когда выдаётся | Чего стоит | |---|---|---| | виртуальный (vxid) | при старте любой транзакции | ничего, переиспользуется | | реальный (xid) | при первой записи | расходует счётчик транзакций | **Виртуальный xid** - это пара «номер бэкенда / локальный счётчик», например `3/12`. Он выдаётся мгновенно и бесплатно, живёт только пока открыта транзакция, и после неё номер свободно переиспользуется. Глобальный счётчик транзакций он не двигает. **Реальный xid** нужен, только когда транзакция собирается оставить след в данных: вставить, обновить или удалить строку. Тогда ей нужно проштамповать версии своим `xmin`/`xmax` (см. [xmin-xmax](/courses/postgres/kb/xmin-xmax.md)), а для этого требуется настоящий номер. ## Увидеть разницу ```sql BEGIN; SELECT pg_current_xact_id_if_assigned(); -- пусто: только читали, xid не выдан INSERT INTO mv VALUES (9, 'write'); SELECT pg_current_xact_id_if_assigned(); -- теперь число: запись потребовала xid COMMIT; ``` Пока транзакция ничего не записала, `pg_current_xact_id_if_assigned()` возвращает пусто - реального xid у неё нет. Первая же запись его назначает. Функция `pg_current_xact_id()` назначила бы xid принудительно, поэтому в диагностике берут вариант `..._if_assigned`, чтобы случайно не сжечь номер. ## Зачем это знать Из этого следуют две практичные вещи. Первая: длинная **читающая** транзакция не двигает счётчик и сама по себе не приближает wraparound (хотя горизонт всё равно держит - но это про vacuum). Вторая: в `pg_locks` у транзакции всегда есть строка `virtualxid`, а строка `transactionid` появляется только после первой записи. Поэтому по наличию реального xid можно понять, писала транзакция или только читала. ## Команды ```sql SELECT pg_current_xact_id_if_assigned(); ``` Реальный xid, если уже выдан; пусто - транзакция только читала ```sql SELECT virtualtransaction, transactionid FROM pg_locks WHERE pid = pg_backend_pid(); ``` Виртуальный xid есть всегда, реальный - только после записи ```sql SELECT backend_xid, backend_xmin FROM pg_stat_activity WHERE pid = pg_backend_pid(); ``` backend_xid пуст у читающей транзакции, backend_xmin держит горизонт ## См. также - [xmin, xmax и правила видимости](/courses/postgres/kb/xmin-xmax.md) - [Снимок данных (snapshot)](/courses/postgres/kb/snapshot.md) - [Вложенные транзакции и savepoints](/courses/postgres/kb/subtransactions.md)