To do more powerful things, we must use a cursor.
Within the transaction, a cursor can be created with
mdb_cursor_open(). With this cursor we can store/retrieve/delete
(multiple) values using mdb_cursor_get(), mdb_cursor_put(),
and mdb_cursor_del().
mdb_cursor_get() positions itself depending on the cursor
operation requested, and for some operations, on the supplied key. For
example, to list all key/value pairs in a database, use operation
MDB_FIRST for the first call to mdb_cursor_get(), and
MDB_NEXT on subsequent calls, until the end is hit.
To retrieve all keys starting from a specified key value, use
MDB_SET. For more cursor operations, see the LMDB API
docs.
When using mdb_cursor_put(), either the function will
position the cursor for you based on the key, or you can use
operation MDB_CURRENT to use the current position of the cursor. Note
that key must then match the current position's key.
So we have a cursor in a transaction which opened a database in an
environment which is opened from a filesystem after it was separately
created.
Or, we create an environment, open it from a filesystem, create a
transaction within it, open a database within that transaction, and create a
cursor within all of the above.
Got it?
LMDB uses POSIX locks on files, and these locks have issues if one
process opens a file multiple times. Because of this, do not
mdb_env_open() a file multiple times from a single process. Instead,
share the LMDB environment that has opened the file across all threads.
Otherwise, if a single process opens the same environment multiple times,
closing it once will remove all the locks held on it, and the other
instances will be vulnerable to corruption from other processes.
Also note that a transaction is tied to one thread by default
using Thread Local Storage. If you want to pass read-only transactions
across threads, you can use the MDB_NOTLS option on the
environment.
To actually get anything done, a transaction must be committed
using mdb_txn_commit(). Alternatively, all of a transaction's
operations can be discarded using mdb_txn_abort(). In a read-only
transaction, any cursors will not automatically be freed. In a
read-write transaction, all cursors will be freed and must not be used
again.
For read-only transactions, obviously there is nothing to commit
to storage. The transaction still must eventually be aborted to close any
database handle(s) opened in it, or committed to keep the database handles
around for reuse in new transactions.
In addition, as long as a transaction is open, a consistent view
of the database is kept alive, which requires storage. A read-only
transaction that no longer requires this consistent view should be
terminated (committed or aborted) when the view is no longer needed (but see
below for an optimization).
There can be multiple simultaneously active read-only transactions
but only one that can write. Once a single read-write transaction is opened,
all further attempts to begin one will block until the first one is
committed or aborted. This has no effect on read-only transactions, however,
and they may continue to be opened at any time.
mdb_get() and mdb_put() respectively have no and
only some support for multiple key/value pairs with identical keys. If there
are multiple values for a key, mdb_get() will only return the first
value.
When multiple values for one key are required, pass the
MDB_DUPSORT flag to mdb_dbi_open(). In an MDB_DUPSORT
database, by default mdb_put() will not replace the value for a key
if the key existed already. Instead it will add the new value to the key. In
addition, mdb_del() will pay attention to the value field too,
allowing for specific values of a key to be deleted.
Finally, additional cursor operations become available for
traversing through and retrieving duplicate values.
If you frequently begin and abort read-only transactions, as an
optimization, it is possible to only reset and renew a transaction.
mdb_txn_reset() releases any old copies of data kept around
for a read-only transaction. To reuse this reset transaction, call
mdb_txn_renew() on it. Any cursors in this transaction must also be
renewed using mdb_cursor_renew().
Note that mdb_txn_reset() is similar to
mdb_txn_abort() and will close any databases you opened within the
transaction.
To permanently free a transaction, reset or not, use
mdb_txn_abort().
For read-only transactions, any cursors created within it must be
closed using mdb_cursor_close().
It is very rarely necessary to close a database handle, and in
general they should just be left open.
The full LMDB API documentation lists further details, like
how to:
- size a database (the default limits are intentionally small)
- drop and clean a database
- detect and report errors
- optimize (bulk) loading speed
- (temporarily) reduce robustness to gain even more speed
- gather statistics about the database
- define custom sort orders