- Why Transactions?
- Terminology
- Application Structure
- Opening the Environment
- Opening the Databases
- Recoverability and Deadlock Avoidance
- Atomicity
- Repeatable Reads
- Transactional Cursors
- Nested Transactions
- Environment Infrastructure
- Deadlock Detection
- Performing Checkpoints
- Database and Log File Archival Procedures
- Log File Removal
- Recovery Procedures
- Recovery and Filesystem Operations
- Berkeley DB Recoverability
- Transaction Throughput
Deadlock Detection
The first component of the infrastructure, deadlock detection, is not so much a requirement specific to transaction-protected applications, but instead is necessary for almost all applications in which more than a single thread of control will be accessing the database at one time. Although Berkeley DB automatically handles database locking, it is normally possible for deadlock to occur. It is not required by all transactional applications, but exceptions are rare.
When the deadlock occurs, two (or more) threads of control each request additional locks that can never be granted because one of the threads of control waiting holds the requested resource.
For example, consider two processes: A and B. Let's say that A obtains an exclusive lock on item X, and B obtains an exclusive lock on item Y. Then, A requests a lock on Y, and B requests a lock on X. A will wait until resource Y becomes available and B will wait until resource X becomes available. Unfortunately, because both A and B are waiting, neither will release the locks they hold and neither will ever obtain the resource on which it is waiting. In order to detect that deadlock has happened, a separate process or thread must review the locks currently held in the database. If deadlock has occurred, a victim must be selected, and that victim will then return the error DB_LOCK_DEADLOCK from whatever Berkeley DB call it was making.
Berkeley DB provides a separate UNIX-style utility that can be used to perform this deadlock detection, named db_deadlock. Alternatively, applications can create their own deadlock utility or thread using the underlying lock_detect function, or specify that Berkeley DB run the deadlock detector internally whenever there is a conflict over a lock (see DBENV[Right Arrow]set_lk_detect for more information). The following code fragment does the latter:
void env_open(DB_ENV **dbenvp) { DB_ENV *dbenv; int ret; /* Create the environment handle. */ if ((ret = db_env_create(&dbenv, 0)) != 0) { fprintf(stderr, "txnapp: db_env_create: %s\n", db_strerror(ret)); exit (1); } /* Set up error handling. */ dbenv_set_errpfx(dbenv, "txnapp"); /* Do deadlock detection internally. */ if ((ret = dbenv_set_lk_detect(dbenv, DB_LOCK_DEFAULT)) != 0) { dbenv_err(dbenv, ret, "set_lk_detect:DB_LOCK_DEFAULT"); exit (1); } /* * Open a transactional environment: * create if it doesn't exist * free-threaded handle * run recovery * read/write owner only */ if ((ret = dbenv_open(dbenv, ENV_DIRECTORY, DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_RECOVER | DB_THREAD, S_IRUSR | S_IWUSR)) != 0) { dbenv >err(dbenv, ret, "dbenv_open: %s", ENV_DIRECTORY); exit (1); } *dbenvp = dbenv; }
Deciding how often to run the deadlock detector and which of the deadlocked transactions will be forced to abort when the deadlock is detected is a common tuning parameter for Berkeley DB applications.