First steps
Related: Installation and building · Tooling · Restrictions · Textbook: Volume I "Fundamentals" · C API reference
Everything starts with an environment (MDBX_env): mdbx_env_create() →
mdbx_env_open() → work → mdbx_env_close(). A non-zero mode argument of
mdbx_env_open() allows creating the database and the directory when they do
not exist and specifies the file mode bits for the new files.
Two files appear in the directory: the lock file (LCK) and the storage
file (DXB). If you do not want a directory — the MDBX_NOSUBDIR option: the
given path is used directly as the data file, with a -lck-suffixed file next
to it for locking.
A transaction
Within the environment a transaction is created (mdbx_txn_begin()):
read-write or read-only; write transactions may be nested. A transaction must
be used by one thread at a time. Transactions are always required, even for
read-only access — they provide the consistent view of the data.
A table
Within the transaction a table is opened (mdbx_dbi_open()) — a key-value
space inside the environment. If only one table will ever be used, a NULL
name may be passed. For named databases the MDBX_CREATE flag creates the
table when it does not exist, and mdbx_env_set_maxdbs() must be called after
mdbx_env_create() and before mdbx_env_open() to set the budget of named
tables.
A single transaction can open multiple tables. Generally tables should be opened once, by the first transaction of the process.
Reading and writing
Inside the transaction mdbx_get() and mdbx_put() work with key-value
pairs. A pair is two MDBX_val structures (similar to POSIX struct iovec):
iov_base (the data pointer) and iov_len (the length). Unlike LMDB, libmdbx
supports zero-length keys and values.
Since libmdbx is very efficient (usually zero-copy), the data returned in an
MDBX_val may be memory-mapped straight from disk: look but do not touch
(and do not free()). Once the transaction is closed the values can no longer
be used — copy them if you need to keep them.
Cursors
For more powerful things — a cursor (mdbx_cursor_open() within the
transaction): mdbx_cursor_get(), mdbx_cursor_put(), mdbx_cursor_del().
mdbx_cursor_get() positions itself by the requested operation (and, for some
of them, by the key): to list all pairs — MDBX_FIRST, then MDBX_NEXT until
the end; to retrieve all keys starting from a given one — MDBX_SET. The full
operation list — the
reference.
mdbx_cursor_put() either positions the cursor by the key itself, or you use
the MDBX_CURRENT operation for the current position (the key must then match
the current position's key).
Commit and rollback
A transaction is committed by mdbx_txn_commit() or entirely discarded by
mdbx_txn_abort().
Important (a difference from LMDB): opened cursors can be reused and must be closed explicitly — regardless of whether they were opened within a read-only or a write transaction. This removes ambiguity and the whole class of use-after-free/double-free errors.
Important (a difference from LMDB): handles of opened tables become immediately available to other transactions — regardless of whether the transaction will be aborted or reset.
Multiple read-only transactions may be active simultaneously, but only one can write: further attempts to begin a write transaction block until the current one is committed or aborted. Reads are unaffected.
Threads and processes
- Do not open a database twice in the same process — libmdbx tracks and
prevents this. Share the opened environment across all threads. The LMDB
compatible escape hatch (multi-open) is the
MDBX_DBG_LEGACY_MULTIOPENoption viamdbx_setup_debug()before calling other functions; lock recovery may cause pauses. - Do not use the environment in a child after
fork()— libmdbx checks this at critical points. If needed —mdbx_env_resurrect_after_fork(). - No more than one transaction per thread. Violations are reported with
MDBX_TXN_OVERLAPPING,MDBX_BAD_RSLOT,MDBX_BUSY— unless theMDBX_NOSTICKYTHREADSoption is set (transaction hand-off between threads; with it you must know exactly what you are doing). A write transaction must be committed/aborted in the thread that started it — otherwiseMDBX_THREAD_MISMATCH.
Duplicate keys (multimaps)
mdbx_get() returns only the first value of a key. For multiple values open
the table with MDBX_DUPSORT: then mdbx_put() adds the value to the key
(instead of replacing), mdbx_del() honors the value field (point-wise
deletion), and cursors gain extra operations for traversing duplicates.
Read optimization
If read-only transactions are frequently begun and aborted — use
mdbx_txn_reset() (releases the old data copies) + mdbx_txn_renew()
(resumption); cursors — mdbx_cursor_renew()/mdbx_cursor_close(). To
permanently finish a transaction — mdbx_txn_abort().
Cleaning up
All created cursors are closed with mdbx_cursor_close() (see the important
note above). Table handles are usually left open: closing makes them
unavailable to all transactions of the environment — do not close a handle
while at least one transaction is using it.
Where to go next
- Database size management:
mdbx_env_set_geometry(). - Bulk loading:
MDBX_MULTIPLE,MDBX_APPEND. - LIFO reclaiming speedup on write-back storage:
MDBX_LIFORECLAIM. - Range query result estimation:
mdbx_estimate_*. - Resolving a "database full" caused by slow readers:
mdbx_env_set_hsr(). - Sequences and canary markers:
mdbx_dbi_sequence(),MDBX_canary. - Durability modes — the reliability/performance trade-offs.
- Textbook: Volume I — the systematic exposition.