---
title: "🗄️ LMDB: Введение"
date: 2024-06-25T21:59:48+03:00
draft: true
tags: [db, tutorial]
---

Этот пост является моим вольным (_yandex-translate_) переводом страницы из документации LMDB:
http://www.lmdb.tech/doc/starting.html.

# Введение в LMDB

LMDB компактная, быстрая, мощная и надежная.
LMDB представляет из себя упрощенный вариант API Berkeley DB (BDB).

После прочтения этой страницы основная документация по LMDB API должна стать понятной.
Спасибо Берту Хьюберту за создание первоначальной версии этого обзора.

Все начинается с среды, созданной с помощью `mdb_env_create()`.
После создания эта среда также должна быть открыта с помощью `mdb_env_open()`.

`mdb_env_open()` получает имя, которое интерпретируется как путь к каталогу.
Обратите внимание, что этот каталог должен уже существовать, он не будет создан за вас.
В этом каталоге будут созданы файл блокировки и файл хранения данных.
Если вы не хотите использовать каталог, вы можете указать параметр `MDB_NOSUBDIR`,
и в этом случае указанный вами путь будет использоваться непосредственно в качестве файла данных,
а в качестве файла блокировки будет использоваться другой файл с добавленным суффиксом «`-lock`».

Как только среда открыта, в ней можно создать транзакцию с помощью функции `mdb_txn_begin()`.
Транзакции могут быть доступны для чтения или только для записи,
а транзакции чтения-записи могут быть вложенными.
Транзакция должна использоваться только одним потоком одновременно.
Транзакции требуются всегда, даже для доступа только для чтения.
Транзакция обеспечивает согласованное представление данных.

Как только транзакция создана, база данных может быть открыта в ней с помощью функции `mdb_dbi_open()`.
Если в среде будет использоваться только одна база данных,
в качестве имени базы данных может быть передано значение `NULL`.
Для именованных баз данных необходимо использовать флаг `MDB_CREATE` для создания базы данных, если она еще не существует.
Кроме того, функция `mdb_env_set_max dps()` должна вызываться после функции `mdb_env_create()`
и перед функцией `mdb_env_open()`, чтобы задать максимальное количество именованных баз данных, которые вы хотите поддерживать.

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

В рамках транзакции `mdb_get()` и `mdb_put()` могут хранить отдельные пары ключ/значение,
если это все, что вам нужно сделать (но смотрите раздел `Cursors` ниже, если вы хотите сделать больше).


## Курсоры (Cursors)

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

Внутри транзакции курсор может быть создан с помощью функции `mdb_cursor_open()`.
С помощью этого курсора мы можем сохранять/извлекать/удалять (несколько) значений,
используя `mdb_cursor_get()`, `mdb_cursor_put()` и `mdb_cursor_del()`.

Функция `mdb_cursor_get()` позиционирует себя в зависимости от запрашиваемой операции с курсором,
а для некоторых операций - от предоставленного ключа.
Например, чтобы вывести список всех пар ключ/значение в базе данных,
используйте операцию `MDB_FIRST` для первого вызова функции `mdb_cursor_get()` и `MDB_NEXT` для последующих вызовов,
пока не будет достигнут конец.

Чтобы получить все ключи, начиная с указанного значения ключа, используйте `MDB_SET`.
Дополнительные операции с курсором см. в документации
[IMDB API](http://www.lmdb.tech/doc/group__mdb.html).

При использовании функции `mdb_cursor_put()` либо функция установит курсор для вас на основе **ключа**,
либо вы можете использовать операцию `MDB_CURRENT` для использования текущей позиции курсора.
Обратите внимание, что в этом случае ключ должен соответствовать ключу текущей позиции.


### Подведение итогов

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

Или же мы создаем среду, открываем ее из файловой системы, создаем в ней транзакцию,
открываем базу данных в рамках этой транзакции и создаем курсор во всем вышеперечисленном.

Понятно?


## Потоки и процессы

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

Также обратите внимание, что транзакция по умолчанию привязана к одному потоку, используя локальное хранилище потоков.
Если вы хотите передавать транзакции только для чтения между потоками, вы можете использовать параметр `MDB_NOTLS` в среде.


## Транзакции, откаты и т.д.

Чтобы действительно что-то сделать, транзакция должна быть зафиксирована с помощью функции `mdb_txn_commit()`.
В качестве альтернативы, все операции транзакции могут быть отменены с помощью функции `mdb_txn_abort()`.
В транзакции только для чтения никакие курсоры автоматически освобождаться не будут.
В транзакции чтения-записи все курсоры будут освобождены и не должны использоваться повторно.

Очевидно, что в транзакциях только для чтения нет ничего, что можно было бы сохранить.
Транзакция все равно должна быть в конечном итоге прервана, чтобы закрыть все открытые в ней дескрипторы базы данных,
или зафиксирована, чтобы сохранить дескрипторы базы данных для повторного использования в новых транзакциях.

Кроме того, пока транзакция открыта, поддерживается согласованное представление базы данных, для чего требуется хранение.
Транзакция только для чтения, для которой больше не требуется это согласованное представление,
должна быть завершена (зафиксирована или прервана), когда представление больше не требуется (но смотрите ниже для оптимизации).

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


## Дублирование ключей

Функции `mdb_get()` и `mdb_put()`, соответственно, не поддерживают несколько пар ключ/значение с одинаковыми ключами
и поддерживают их только в некоторой степени. Если у ключа несколько значений, функция `mdb_get()` вернет только первое значение.

Если требуется несколько значений для одного ключа, передайте флаг `MDB_SUPPORT` в функцию `mdb_dbi_open()`.
В базе данных `MDB_SUPPORT` по умолчанию функция `mdb_put()` не заменяет значение ключа,
если ключ уже существует. Вместо этого она добавит новое значение к ключу.
Кроме того, функция `mdb_del()` также обратит внимание на поле `value`, позволяя удалять определенные значения ключа.

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


## Некоторая оптимизация

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

Функция `mdb_txn_reset()` освобождает все старые копии данных, сохраненные для транзакции только для чтения.
Чтобы повторно использовать эту транзакцию сброса, вызовите для нее функцию `mdb_txn_renew()`.
Все курсоры в этой транзакции также должны быть обновлены с помощью функции `mdb_cursor_renew()`.

Обратите внимание, что функция `mdb_txn_reset()` аналогична функции `mdb_txn_abort()`
и закроет все базы данных, открытые вами в рамках транзакции.

Чтобы окончательно освободить транзакцию, независимо от того, сбрасывается она или нет, используйте функцию `mdb_txn_abort()`.


## Завершение

Для транзакций, доступных только для чтения, все созданные в нем курсоры должны быть закрыты с помощью функции `mdb_cursor_close()`.

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


## В полном API

В полной документации по API LMDB приведены дополнительные сведения, например, как:

* Увеличить размер базы данных (ограничения по умолчанию намеренно невелики)
* Удалить и очистить базу
* Выявление и сообщения ошибок
* Оптимизация скорости загрузки
* (временно) Уменьшить надежность, чтобы увеличить скорость
* Сбор статистики о базе данных
* Определение пользовательских порядков сортировки
