166 lines
15 KiB
Markdown
166 lines
15 KiB
Markdown
---
|
||
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 приведены дополнительные сведения, например, как:
|
||
|
||
* Увеличить размер базы данных (ограничения по умолчанию намеренно невелики)
|
||
* Удалить и очистить базу
|
||
* Выявление и сообщения ошибок
|
||
* Оптимизация скорости загрузки
|
||
* (временно) Уменьшить надежность, чтобы увеличить скорость
|
||
* Сбор статистики о базе данных
|
||
* Определение пользовательских порядков сортировки
|