---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.6
alternate:
  - https://ytsaurus.tech/docs/en/user-guide/storage/transactions.md
  - https://ytsaurus.tech/docs/ru/user-guide/storage/transactions.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ytsaurus.tech/docs/ru/llms.txt

<!-- source: ru/_includes/user-guide/storage/transactions.md -->
# Транзакции

В данном разделе описаны транзакции, применительно к [Кипарису](https://ytsaurus.tech/docs/ru/user-guide/storage/cypress.md) и [статическим таблицам](https://ytsaurus.tech/docs/ru/user-guide/storage/static-tables.md), [блокировки](#locks) и [версионированность](#versioning) объектов Кипариса.
Транзакционная модель динамических таблиц описана в разделе [Мультиверсионность и транзакционность динамических таблиц](https://ytsaurus.tech/docs/ru/user-guide/dynamic-tables/transactions.md).

## Общие сведения { #transactions }

Система YTsaurus поддерживает транзакции, но имеет ряд отличий от классической [транзакционной системы](https://ru.wikipedia.org/wiki/Транзакционная_система):

- транзакции могут длиться минуты и часы;
- изолированность можно настраивать;
- внешние ключи таблиц отсутствуют;
- помимо таблиц транзакции могут затрагивать другие узлы Кипариса.

YTsaurus обеспечивает следующие свойства транзакционной системы:

- **Атомарность (Atomicity)**. YTsaurus гарантирует, что транзакция не будет зафиксирована в системе частично. Будут либо выполнены все её подоперации, либо не выполнено ни одной. Изменение данных одного узла Кипариса в рамках одной команды (например, команды set) атомарно.
- **Согласованность (Consistency)**. Транзакция, фиксирующая свои результаты, сохраняет согласованность данных в узлах Кипариса и статических таблицах.
- **Изолированность (Isolation)**. В отличие от традиционных транзакционных систем, YTsaurus позволяет управлять изолированностью на уровне транзакции за счет разных режимов блокировок. Поведение YTsaurus соответствует уровню изоляции [Read committed](https://ru.wikipedia.org/wiki/Уровень_изолированности_транзакций) при взаимодействии с Кипарисом и [Serializable](https://ru.wikipedia.org/wiki/Уровень_изолированности_транзакций) при взаимодействии со статическими таблицами в рамках транзакций.
- **Долговечность (Durability)**. YTsaurus гарантирует сохранность изменений после фиксации транзакции. Сбои в оборудовании одного сервера, обесточивание или выключение системы не могут привести к потере сделанных изменений. Транзакции в системе могут переживать отключение системы и продолжать выполнение после восстановления системы.

При выполнении большинства действий в системе можно указать, в какой транзакции следует выполнять эти действия. Если транзакция не указана явно, элементарные действия с тем или иным узлом Кипариса, например, создание узла, чтение и запись его атрибутов, выполняются атомарно, однако для более сложных действий транзакцию нужно начинать явно. Одним таким примером является чтение таблицы: в отсутствие транзакции и snapshot-лока на таблицу нет гарантии, что данные не удалят «под ногами». Тем не менее, если данные физически существуют и их удается прочитать (одним запросом), то прочитано будет консистентное состояние таблицы.

Транзакции представляют собой объекты типа `transaction`. Список всех транзакций системы можно найти в Кипарисе, по адресу `//sys/transactions`.
У транзакции может быть родитель — другая транзакция. Транзакции образуют дерево, корнями которого являются транзакции без родителей, называемые также транзакциями верхнего уровня. Список всех транзакций верхнего уровня доступен по адресу `//sys/topmost_transactions`.

Транзакции делятся на **мастер-транзакции** и **таблет-транзакции**. Мастер-транзакции позволяют выполнять операции с метаинформацией мастера. Таблет-транзакции — только записывать данные в динамические таблицы.

## Мастер-транзакции { #master_transactions }

Транзакционность в пределах мастер-серверов распространяется на версионируемые [объекты](https://ytsaurus.tech/docs/ru/user-guide/storage/objects.md). В качестве примера таких объектов могут выступать файлы, таблицы и каталоги.

### Создание транзакции { #create }

Чтобы создать транзакцию, выполните команду `start_tx`.
При этом можно указать родительскую транзакцию в опции `parent_id`, и время жизни транзакции — в опции `timeout`.
Верхнее ограничение времени жизни транзакции в системе по умолчанию равно одному часу. Если указать `timeout` более часа, оно окажется равным ограничению.
Время жизни отсчитывается с момента вызова `start_tx` или с момента последнего вызова [`ping_tx`](#ping).

### Продление времени жизни транзакции { #ping }

Чтобы продлить время жизни транзакции, выполните команду `ping_tx`.
Каждое выполнение продлевает время жизни транзакции на интервал времени, равный `timeout`. Если время с момента создания транзакции или последнего выполнения команды `ping_tx` превысит `timeout`, транзакция будет отменена.

### Завершение транзакции { #commit }

Транзакцию можно отменить командой `abort_tx` или успешно завершить командой `commit_tx`.

При отмене транзакции, все вложенные транзакции тоже будут отменены.
Для успешного завершения транзакции с вложенными транзакциями, сначала выполните команду `commit_tx` на всех вложенных транзакциях. Попытка выполнить `commit_tx` сразу на родительской транзакции вызовет ошибку.
Во всех остальных случаях завершение транзакции не может привести к ошибке.

В YTsaurus используются [пессимистические блокировки](#locks), поэтому возможные конфликты обнаруживаются по мере их возникновения — при взятии блокировок и создании объектов внутри транзакций, а не в момент завершения транзакции.

### Атрибуты транзакций { #attributes }

Помимо атрибутов, [присущих всем объектам](https://ytsaurus.tech/docs/ru/user-guide/storage/objects.md#attributes), транзакции имеют следующие атрибуты:

| **Атрибут**              | **Тип**            | **Описание**                                                 | **Обязательный**      |
| ------------------------ | ------------------ | ------------------------------------------------------------ |-----------------------|
| `timeout`                | `integer`          | Таймаут транзакции в мс Может отсутствовать для некоторых системных транзакций. | Нет |
| `title`                  | `string`           | Текстовая строка-описание. Данный атрибут заполняется автоматически для всех системных транзакций, а для пользовательских — только если пользователь самостоятельно укажет его при создании транзакции. | Нет |
| `last_ping_time`         | `DateTime`         | Время последнего продления жизни транзакции. Может отсутствовать для некоторых системных транзакций. | Нет |
| `parent_id`              | `Guid`             | Идентификатор родительской транзакции.                       | Да |
| `start_time`             | `DateTime`         | Время создания транзакции.                                    | Да |
| `nested_transaction_ids` | `array<Guid>`      | Список идентификаторов вложенных транзакций.                  | Да |
| `staged_object_ids`      | `array<Guid>`      | Список идентификаторов объектов, которыми временно владеет транзакция. | Да |
| `branched_node_ids`      | `array<Guid>`      | Список идентификаторов ответвленных узлов Кипариса.           | Да |
| `locked_node_ids`        | `array<Guid>`      | Список идентификаторов заблокированных узлов Кипариса.        | Да |
| `lock_ids`               | `array<Guid>`      | Список идентификаторов блокировок, созданных в транзакции.    | Да |
| `resource_usage`         | `ResourceUsageMap` | Атрибут, показывающий использование ресурсов в данной транзакции для каждого затронутого аккаунта. |  Да |

{% note info "Примечание" %}

Транзакции, созданные системой, всегда имеют заполненный атрибут `title`. Он содержит описание процесса, создавшего транзакцию.
Пользователям также рекомендуется использовать данный атрибут для описания назначения транзакции.

{% endnote %}

## Блокировки { #locks }

Версионирование узлов Кипариса связано с понятием блокировки. Беря блокировку на узел, транзакция выражает намерение работать с узлом в соответствии с [режимом блокировки](#locking_mode) и в случае успеха гарантируется, что:
- работать указанным образом с данным узлом разрешается;
- узел для данной транзакции ответвлен.

{% note info "Примечание" %}

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

Из наличия блокировки на узле всегда следует наличие ответвленной версии этого узла. Однако обратное верно не всегда: в определенных сценариях могут возникать ответвленные, но не заблокированные узлы. При этом попытка работать с таким узлом всегда приведет к взятию блокировки над ним. Ответвление и взятие блокировки — связанные, но разные вещи.

{% endnote %}

Блокировка является полноценным объектом и обладает идентификатором. Список всех блокировок в системе доступен по адресу `//sys/locks`. Для обращения к конкретной блокировке рекомендуется использовать адрес вида `#lock-id`.

Список блокировок, взятых транзакцией, отображается в ее атрибуте `lock_ids`.

### Режимы блокировок { #locking_mode }

Существуют следующие режимы блокировок: `snapshot`, `exclusive` и `shared`.
Режим блокировки определяет список допустимых действий транзакции, а также возможность взять другие блокировки:

- `snapshot` - транзакция получает возможность читать, но не модифицировать узел. Блокировка используется, чтобы взять read-only копию состояния узла Кипариса в контексте транзакции и зафиксировать состояние данного узла.

  {% note info "Примечание" %}

  ​    Snapshot-блокировка берется только на сам узел, но не на путь до него в Кипарисе. Если продолжать обращаться к узлу по его пути, можно получить новый узел, размещенный по тому же пути.
      Чтобы гарантированно обратиться к snapshot-версии узла, используйте `id` данного узла, который возвращает [команда](https://ytsaurus.tech/docs/ru/api/commands.md#lock) `lock`.

  {% endnote %}

- `exclusive` - транзакция получает возможность модифицировать состояние узла. При этом другие транзакции не могут менять узел.

- `shared` - транзакция получает возможность модифицировать определенную часть состояния узла. У других транзакций при этом остается возможность изменять другие части этого узла.

   Существует три стандартных сценария использования данной блокировки:
   - Одновременное добавление данных в таблицу или файл из нескольких транзакций. При этом возможно только добавление, перезапись невозможна.
   - Одновременное создание нескольких различающихся по имени подкаталогов в пределах одного каталога из нескольких транзакций. Например, транзакция T1 может начаться и создать (или удалить) узел `//tmp/a`, а транзакция T2 — начаться и создать (или удалить) `//tmp/b`. При этом каждая из них возьмет по отдельной `shared`-блокировке на `//tmp`. Для выявления конфликтов каждая блокировка имеет атрибут `child_key`, указывающий на то, какой ключ (подкаталог) ей заблокирован.
   - Одновременное создание нескольких различающихся по имени атрибутов в пределах одного узла из нескольких транзакций. Например, транзакция T1 может начаться и установить (изменить, удалить) атрибут `//tmp/@a`, а транзакция T2 — начаться и установить (изменить, удалить) атрибут `//tmp/@b`. Для выявления конфликтов каждая блокировка имеет атрибут `attribute_key`, указывающий на то, какой ключ (имя атрибута) ей заблокирован.

{% note info "Примечание" %}

Транзакции бывают вложенными. Под «другими транзакциями» здесь понимаются транзакции, неродственные данной, т. е. не имеющие с ней общего предка. Из-за вложенных транзакций, в частности, на узле может быть более одной `exclusive` блокировки.

{% endnote %}

### Неявные блокировки { #implicit_locks}

Транзакция может брать блокировки как явно, с помощью команды `lock`, так и неявно. Неявное взятие блокировок может возникать при определенных взаимодействиях с узлами Кипариса, например:

- Создание узла сопровождается взятием `exclusive` блокировки.
- Запись в таблицу или файл означает взятие `shared` блокировки, если происходит добавление данных в таблицу, и `exclusive` блокировки, если происходит ее перезапись.
- Создание новой записи в каталоге, а также изменение или удаление существующей записи сопровождаются взятием `shared` блокировки с соответствующим `child_key`.
- Создание нового атрибута узла, а также изменение или удаление существующего атрибута сопровождаются взятием `shared` блокировки с соответствующим `attribute_key`.

### Совместимость блокировок { #locks_compatibility}

Некоторые комбинации блокировок могут быть взяты одновременно. Формальные правила следующие:

- `snapshot` блокировку можно взять всегда. Если данной транзакцией `snapshot` блокировка уже взята, попытка повторного взятия завершается без ошибок и не имеет эффекта.
- `shared` или `exclusive` блокировку нельзя взять, если транзакцией или любым ее предком уже взята `snapshot` блокировка.
- `shared` или `exclusive` блокировку нельзя взять, если другой транзакцией, не являющейся предком данной, уже взята `exclusive` блокировка.
- `exclusive` блокировку нельзя взять, если другой транзакцией, не являющейся предком данной, уже взята `shared` блокировка.
- `shared` блокировку с заданным `child_key` нельзя взять, если другой транзакцией, не являющейся предком данной, уже взята `shared` блокировка с тем же `child_key`.
- `shared` блокировку с заданным `attribute_key` нельзя взять, если другой транзакцией, не являющейся предком данной, уже взята `shared` блокировка с тем же `attribute_key`.
- `shared` блокировку без `child_key` и `attribute_key` можно взять, несмотря на любые другие `shared` блокировки.

### Операции над блокировками { #lock_operations}

Для работы с блокировками существуют две команды: `lock` и `unlock`.

Команда `lock` позволяет взять блокировку на узел Кипариса в указанной транзакции.

Команда `unlock` выполняет обратное действие: снимает все явные блокировки с узла для заданной транзакции — как уже взятые, так и еще стоящие в [очереди на взятие блокировки](#locking_queue).
Блокировку можно снять, только если заблокированная ответвленная версия узла не содержит изменений по сравнению с оригиналом. Следовательно, явную `snapshot` блокировку можно снять всегда. В противном случае команда завершится ошибкой.

Блокировки автоматически снимаются по завершении транзакции - как успешном, так и неуспешном. Поэтому снимать их вручную, как правило, нет необходимости.
Команду `unlock` рекомендуется использовать только в одном случае — если нужно брать и отпускать блокировки, не завершая транзакцию. Как правило, такие транзакции призваны управлять синхронизацией сторонних сервисов.

### Очередь на взятие блокировки { #locking_queue }

С каждым узлом Кипариса может быть связана очередь ожидающих блокировок.

По умолчанию команда `lock` пытается взять блокировку, и в случае, если узел уже заблокирован, возвращает ошибку. Если в команде `lock` указать параметр `waitable`, равный `true`, блокировка встанет в очередь.
Чтобы узнать, находится ли блокировка в очереди, запросите атрибут `state`. Если блокировка находится в очереди, он будет равен `pending`, если нет - `acquired`.

{% note warning "Внимание" %}

Чтобы блокировка была действительно взята, например, эксклюзивно, ее состояние должно перейти в `acquired`.
Чтобы взять блокировку:

- вызовите команду `lock`;
- наблюдайте в цикле за `state`, пока он не примет значение `acquired`.

{% endnote %}

### Атрибуты блокировок { #lock_attributes}

Помимо атрибутов, присущих всем объектам, блокировки имеют следующие атрибуты:

| **Атрибут**      | **Тип**  | **Описание**                                                 |
| ---------------- | -------- | ------------------------------------------------------------ |
| `state`          | `string` | Состояние блокировки: `pending` или `acquired`.               |
| `transaction_id` | `Guid`   | Идентификатор транзакции, взявшей блокировку.                |
| `mode`           | `string` | Режим блокировки: `shared`, `exclusive` или `snapshot`.      |
| `child_key`      | `string` | Ключ, по которому взята блокировка. Только для типа `shared`. |
| `attribute_key`  | `string` | Имя атрибута, по которому взята блокировка (только для типа `shared`) |

## Версионирование { #versioning }

Изменение состояния узла транзакцией представляет собой следующий трехфазный процесс:

1. Транзакция `T`  берет на узел `N` блокировку. При этом у узла `N` возникает версия `N:T`, которая образуется следующим образом:

   - для `snapshot` блокировки она ответвляется от версии `N:T'`, где `T'` — ближайший предок `T`, ответвивший `N`;
   - для `shared` и `exclusive` блокировок от `N:T'` ответвляется `N:T''`, где `T''` — дочка `T'` ; от `N:T''` ответвляется `N:T'''`, где `T'''` — дочка `T''` и т.д. вплоть до `N:T`. Иными словами, создается цепь ответвленных друг от друга версий узла для каждой транзакции от `T'` до `T`.

   В случае, если такого `T'` нет, используется реальная версия `N`. Для данного описания не имеет значения, была ли взята блокировка явно или неявно.

2. Транзакция `T` работает с узлом `N`, при этом фактические изменения накапливаются в его версии `N:T`.

3. Транзакция `T` успешно завершается, при этом внесённые ей в `N:T` изменения, если таковые имеются, вливаются в версию, от которой версия `N:T` была ответвлена. Таким образом, эти изменения становятся видны родительской транзакции — или вообще всем, если `T` была транзакцией верхнего уровня.

## Использование транзакций в операциях

При запуске операций планировщик создает набор транзакций, чтобы обеспечить определенную атомарность обработки данных в операции. Подробнее про схему работы можно прочитать в разделе [Транзакционность обработки данных](https://ytsaurus.tech/docs/ru/user-guide/data-processing/operations/overview.md#transactions).
<!-- endsource: ru/_includes/user-guide/storage/transactions.md -->