NexusDB use different kinds of locking to perform optimally in multi user environments.
Content Locks
Content locks can be either record-level (these are always write locks, acquired by calling Edit) or table-level (these can be either read or write locks, acquired by calling LockTable).
Content locks can be contending for the same resource. If so, then an existing record-level content lock can prevent table-level content locks from being granted. Similarly a table-level content lock will prevent a record-level content lock from being granted.
In NexusDB only one cursor can own a write lock on the same table. Many cursors can own a read lock As long as any cursor holds a read lock no write lock can be placed on the table. Table level locks interact with record level locks. Record level locks are always write locks. You cannot acquire a table level read lock if any cursor holds a record lock. You cannot acquire a table level write lock if any OTHER cursor holds a record lock. You can only acquire a record lock if there are no table level locks or if you own a table level write lock.
A table level read lock prevents anyone including yourself from changing the table while you hold the lock. A table level write lock prevents anyone else from changing the table while you hold the lock.
Transaction Locks
Transaction locks are independent from content locks. A shared transaction lock is acquired when you read from a table in the context of a transaction. An exclusive transaction lock is acquired when you write to a table in the context of a transaction.
How are locks implemented?
Each server side table object has 2 different synchronization objects that manage access to the table:
TnxLockContainer
TnxLockContainer handles transaction locking (shared/exclusive locks). This lock container is only used when the table is accessed in the context of a transaction. Read access needs a shared lock. Write access requires an exclusive lock. At any point in time there can be no locks, a single or multiple shared locks or a single exclusive lock. No other combination is possible.
TnxReadWritePortal
TnxReadWritePortal handles thread synchronization. Each thread that wants to read from the table outside of a transaction must acquire a read lock on this portal. (This read lock is always released before a server call returns to the client. It is only acquired for the split second it takes to perform the actual read of a single record). A thread that wants to commit a transaction which has acquired an exclusive lock on the table must acquire a write lock on this portal before it can commit its changes. This is needed to prevent reading threads from accessing the buffer manager while memory pages are copied from the transaction buffers into the real buffers.
In summary there can be different types of access to a table:
Read access
In the context of a transaction this acquires a shared lock until the transaction is completed. Outside a transaction read access acquires a read lock on the table for the split second it takes to actually perform the read.
Write access
This can only be done in the context of a transaction (a transaction is automatically started and immediately committed if no active transaction is present). Write access will acquire an exclusive lock until the transaction is completed and committed. It also requires an active transaction (which has already acquired an exclusive lock, preventing read access from inside other transactions) and acquires a write lock (preventing reads from outside a transaction) for the time it takes to commit the changes to disk.
Transaction isolation levels
First a quick description of the different possible isolation levels:
READ UNCOMMITTED or DIRTY READ
You can read an uncommitted transaction that might get rolled back later. This isolation level is also called a dirty read. This is the lowest isolation level.
READ COMMITTED
This ensures that data that another application has changed and not yet committed can not be read, but it does not ensure that the data will not be changed before the end of the current transaction.
NON REPEATABLE READ
This occurs when a transaction reads the same record more than once and, between the two (or more) reads, a separate transaction modifies that record. Because the record was modified between reads within the same transaction, each read produces different values, which introduces inconsistency.
REPEATABLE READ
When it's used, then dirty reads and no repeatable reads cannot occur.
PHANTOM
Phantom behavior occurs when a transaction attempts to read a record that does not exist and a second transaction inserts the record before the first transaction finishes. If the record is inserted, the record appears as a phantom to the first transaction, inconsistently appearing and disappearing.
SERIALIZABLE
This is the most restrictive isolation level. When used, phantom values cannot occur. It prevents other users from updating or inserting records into the data set until the transaction is complete.
There is only one physical type of transaction in NexusDB. The distinction between implicit and explicit transactions is a virtual one. Any operation that results in write access to a table, checks if a transaction is already present. If not present, then a transaction is started (implicit), the operation performed (insert, modify, delete) and the transaction committed/rolled back depending on the success of the operation.
An explicit transaction is a client side controlled transaction.
Using the above terminology, we can now classify NexusDB as follows:
Dataset state or context |
NexusDB |
outside of an explicit transaction |
read committed |
in the context of an explicit transaction |
serializable |
locking granularity |
table level |