# Неблокирующие запросы

## Ситуации возможных блокировок {: #blocking_factors }

При работе с распределенными SQL-запросами следует принимать в расчет
следующие факторы:

- SQL-запрос может вызывать значительные задержки и блокировать
  транзакционный поток, что будет мешать выполнению других запросов
- SQL-запрос не должен приводить к исчерпанию памяти на отдельных узлах
  кластера
- при объединении результатов локальных запросов в виртуальную таблицу
  на узле-маршрутизаторе нужно следить, чтобы эта таблица не
  разрасталась бесконтрольно

Ситуация с задержками и/или блокировкой транзакционного потока актуальна для:
- DQL-запросов (`SELECT`)
- DML-запросов (`CALL`, `DELETE`, `INSERT`, `UPDATE`)
- использования этих команд в подзапросах

## Включение неблокирующего режима {: #enable }

Неблокирующий режим выполнения SQL позволяет длительно выполняющимся
запросам передавать управление планировщику, предотвращая блокировку
event loop и обеспечивая выполнение других задач на экземпляре.
По умолчанию неблокирующий режим отключен.

Включение и отключение режима выполняется с помощью параметра
[sql_preemption](../db_config.md#sql_preemption).

Интервал кооперативных переключений настраивается параметром
[sql_preemption_interval_us](../db_config.md#sql_preemption_interval_us)
(в микросекундах). Меньшие значения повышают отзывчивость event loop,
но увеличивают накладные расходы на переключения. Большие значения
снижают накладные расходы, но могут ухудшать отзывчивость при
выполнении длительных запросов.

### Примеры {: #usage }

```sql
ALTER SYSTEM SET sql_preemption = true;                -- включить
ALTER SYSTEM SET sql_preemption_interval_us = 500;     -- интервал 500 мкс
```

## Как это работает {: #how_it_works }

В потенциально долгих участках выполнения запроса (на координаторе и узле хранения)
периодически вызывается «кооперативная» передача управления (yield), если
с момента последнего переключения прошло не менее `sql_preemption_interval_us`.

Внутри интерпретатора локального SQL (VDBE) задействован callback, который
может передать управление планировщику при достижении заданного интервала.

!!! tip "Важно"
    Внутри явной локальной транзакции VDBE не делает yield, чтобы не нарушать
    атомарность.

На стороне исполнителя DML при необходимости допускается разбиение длинной
модификации данных на несколько атомарных шагов: перед yield текущие изменения
атомарно фиксируются, после возврата начинается фиксация новой партии данных.
Это позволяет разгрузить event loop, сохранив прогресс выполнения.

## Ограничения и семантика {: #limitations }

При неблокирующем выполнении длительные DML могут быть применены несколькими
шагами. Это повышает отзывчивость узла, но может изменить гранулярность
атомарности по сравнению с единственной большой фиксацией. Планируйте обработку
ошибок и повторов с учетом возможных частичных применений.

Неблокирующий режим не отменяет ограничений на ресурсы и объем промежуточных
данных; используйте параметры из раздела ниже для защиты кластера.

## Параметры для ограничения запросов {: #query_limitations }

Чтобы избежать чрезмерной нагрузки и роста промежуточных данных, используйте
следующие параметры (через `OPTION(...)` в запросах):

- `SQL_VDBE_OPCODE_MAX`, ограничение на максимальное количество команд
 при исполнении локального плана виртуальным движком базы данных
  ([VDBE](https://www.sqlite.org/vdbe.html)) на экземпляре кластера.
- `SQL_MOTION_ROW_MAX`, ограничение на максимальное число строк в
  промежуточной виртуальной таблице, собирающей результаты отдельных
  локальных запросов.

Параметр `SQL_VDBE_OPCODE_MAX` ограничивает счетчик команд при исполнении
скомпилированного запроса из
[опкодов](../../overview/glossary.md#opcode) в VDBE-машине (число команд
прямо пропорционально числу опкодов). Ограничение влияет на количество
обработанных кортежей локальным SQL на узлах кластера в DQL-запросах и
позволяет предотвратить выполнение SQL-команды в тех случаях, если
запрашиваемая таблица слишком велика. Это может сигнализировать
пользователю о необходимости переделать запрос (например, отказаться от
полного сканирования (`SELECT * ...`) такой таблицы в пользу выборке
данных по индексу).

<!--
Скомпилированный план локального SQL-запроса, состоящего из опкодов, 
можно посмотреть так:

```
box.execute([[EXPLAIN SELECT * FROM warehouse]])
---
- metadata:
  - name: addr
    type: integer
  - name: opcode
    type: text
  - name: p1
    type: integer
  - name: p2
    type: integer
  - name: p3
    type: integer
  - name: p4
    type: text
  - name: p5
    type: text
  - name: comment
    type: text
  rows:
  - [0, 'Init', 0, 1, 0, '', '00', null]
  - [1, 'OpenSpace', 1, 1025, 0, '', '00', null]
  - [2, 'IteratorOpen', 1, 0, 1, '', '00', null]
  - [3, 'Rewind', 1, 10, 2, '0', '00', null]
  - [4, 'Column', 1, 0, 2, '', '00', null]
  - [5, 'Column', 1, 1, 3, '', '00', null]
  - [6, 'Column', 1, 2, 4, '', '00', null]
  - [7, 'Column', 1, 3, 5, '', '00', null]
  - [8, 'ResultRow', 2, 4, 0, '', '00', null]
  - [9, 'Next', 1, 4, 0, '', '01', null]
  - [10, 'Halt', 0, 0, 0, '', '00', null]
...
```

В разделе `rows` перечислены опкоды запроса.
Читающие опкоды здесь — номера 4, 5, 6, 7, 8, 9.
Каждое прохождение исполнения запроса через опкод
увеличивает счетчик команд на 1. В зависимости от
количества кортежей в таблице, для читающего опкода
может понадобиться несколько прохождений. Соответственно,
счетчик `SQL_VDBE_OPCODE_MAX` будет практически всегда
выше числа опкодов. Так, в приведенном примере 11 опкодов,
но минимальное значение `SQL_VDBE_OPCODE_MAX` — 15.

 -->

См. также:

  - [The SQLite Bytecode Engine](https://www.sqlite.org/opcode.html)

## Примеры {: #examples }

??? example "Тестовые таблицы"
    Примеры использования команд включают в себя запросы к [тестовым
    таблицам](../legend.md).

Пример совместного использования `SQL_VDBE_OPCODE_MAX` и `SQL_MOTION_ROW_MAX`:


```sql
SELECT * FROM warehouse OPTION(SQL_VDBE_OPCODE_MAX = 30, SQL_MOTION_ROW_MAX = 5);
+----+----------+---------+
| ID | ITEM     | TYPE    |
+=========================+
| 1  | "bricks" | "heavy" |
|----+----------+---------|
| 2  | "bars"   | "light" |
|----+----------+---------|
| 3  | "blocks" | "heavy" |
|----+----------+---------|
| 4  | "piles"  | "light" |
|----+----------+---------|
| 5  | "panels" | "light" |
+----+----------+---------+
(5 rows)
```

Если ограничения не позволяют выполнить запрос, то будет возвращена
ошибка.

При меньших значениях `SQL_VDBE_OPCODE_MAX`:

```
---
- null
- ("Reached a limit on max executed vdbe opcodes. Limit: XX\")
...
```

При слишком низком значении `SQL_MOTION_ROW_MAX`:

```
---
- null
- (Exceeded maximum number of rows (4) in virtual table: Х\")
...
```
