mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-17 01:26:25 +00:00
679 lines
27 KiB
HTML
679 lines
27 KiB
HTML
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|||
|
<head>
|
|||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|||
|
<title>Transaction Example</title>
|
|||
|
<link rel="stylesheet" href="gettingStarted.css" type="text/css" />
|
|||
|
<meta name="generator" content="DocBook XSL Stylesheets V1.73.2" />
|
|||
|
<link rel="start" href="index.html" title="Getting Started with Berkeley DB Transaction Processing" />
|
|||
|
<link rel="up" href="wrapup.html" title="Chapter 6. Summary and Examples" />
|
|||
|
<link rel="prev" href="wrapup.html" title="Chapter 6. Summary and Examples" />
|
|||
|
<link rel="next" href="inmem_txnexample_c.html" title="In-Memory Transaction Example" />
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div xmlns="" class="navheader">
|
|||
|
<div class="libver">
|
|||
|
<p>Library Version 11.2.5.2</p>
|
|||
|
</div>
|
|||
|
<table width="100%" summary="Navigation header">
|
|||
|
<tr>
|
|||
|
<th colspan="3" align="center">Transaction Example</th>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td width="20%" align="left"><a accesskey="p" href="wrapup.html">Prev</a> </td>
|
|||
|
<th width="60%" align="center">Chapter 6. Summary and Examples</th>
|
|||
|
<td width="20%" align="right"> <a accesskey="n" href="inmem_txnexample_c.html">Next</a></td>
|
|||
|
</tr>
|
|||
|
</table>
|
|||
|
<hr />
|
|||
|
</div>
|
|||
|
<div class="sect1" lang="en" xml:lang="en">
|
|||
|
<div class="titlepage">
|
|||
|
<div>
|
|||
|
<div>
|
|||
|
<h2 class="title" style="clear: both"><a id="txnexample_c"></a>Transaction Example</h2>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<p>
|
|||
|
The following code provides a fully functional example of a
|
|||
|
multi-threaded transactional DB application. For improved
|
|||
|
portability across platforms, this examples uses pthreads to
|
|||
|
provide threading support.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The example opens an environment and database and then creates 5
|
|||
|
threads, each of which writes 500 records to the database. The keys
|
|||
|
used for these writes are pre-determined strings, while the data is
|
|||
|
a random value. This means that the actual data is arbitrary and
|
|||
|
therefore uninteresting; we picked it only because it requires
|
|||
|
minimum code to implement and therefore will stay out of the way of
|
|||
|
the main points of this example.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Each thread writes 10 records under a single transaction
|
|||
|
before committing and writing another 10 (this is repeated 50
|
|||
|
times). At the end of each transaction, but before committing, each
|
|||
|
thread calls a function that uses a cursor to read every record in
|
|||
|
the database. We do this in order to make some points about
|
|||
|
database reads in a transactional environment.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Of course, each writer thread performs deadlock detection as
|
|||
|
described in this manual. In addition, normal recovery is performed
|
|||
|
when the environment is opened.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
We start with our normal <code class="literal">include</code> directives:
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// File TxnGuide.cpp
|
|||
|
|
|||
|
// We assume an ANSI-compatible compiler
|
|||
|
#include <db_cxx.h>
|
|||
|
#include <pthread.h>
|
|||
|
#include <iostream>
|
|||
|
|
|||
|
#ifdef _WIN32
|
|||
|
extern int getopt(int, char * const *, const char *);
|
|||
|
#else
|
|||
|
#include <unistd.h>
|
|||
|
#endif </pre>
|
|||
|
<p>
|
|||
|
We also need a directive that we use to identify how many threads we
|
|||
|
want our program to create:
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// Run 5 writers threads at a time.
|
|||
|
#define NUMWRITERS 5 </pre>
|
|||
|
<p>
|
|||
|
Next we declare a couple of global
|
|||
|
variables (used by our threads), and we provide our forward
|
|||
|
declarations for the functions used by this example.
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// Printing of pthread_t is implementation-specific, so we
|
|||
|
// create our own thread IDs for reporting purposes.
|
|||
|
int global_thread_num;
|
|||
|
pthread_mutex_t thread_num_lock;
|
|||
|
|
|||
|
// Forward declarations
|
|||
|
int countRecords(Db *, DbTxn *);
|
|||
|
int openDb(Db **, const char *, const char *, DbEnv *, u_int32_t);
|
|||
|
int usage(void);
|
|||
|
void *writerThread(void *); </pre>
|
|||
|
<p>
|
|||
|
We now implement our usage function, which identifies our only command line
|
|||
|
parameter:
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// Usage function
|
|||
|
int
|
|||
|
usage()
|
|||
|
{
|
|||
|
std::cerr << " [-h <database_home_directory>]" << std::endl;
|
|||
|
return (EXIT_FAILURE);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
With that, we have finished up our program's housekeeping, and we can
|
|||
|
now move on to the main part of our program. As usual, we begin with
|
|||
|
<code class="function">main()</code>. First we declare all our variables, and
|
|||
|
then we initialize our DB handles.
|
|||
|
</p>
|
|||
|
<pre class="programlisting">int
|
|||
|
main(int argc, char *argv[])
|
|||
|
{
|
|||
|
// Initialize our handles
|
|||
|
Db *dbp = NULL;
|
|||
|
DbEnv *envp = NULL;
|
|||
|
|
|||
|
pthread_t writerThreads[NUMWRITERS];
|
|||
|
int ch, i;
|
|||
|
u_int32_t envFlags;
|
|||
|
char *dbHomeDir;
|
|||
|
|
|||
|
// Application name
|
|||
|
const char *progName = "TxnGuide";
|
|||
|
|
|||
|
// Database file name
|
|||
|
const char *fileName = "mydb.db"; </pre>
|
|||
|
<p>
|
|||
|
Now we need to parse our command line. In this case, all we want is to
|
|||
|
know where our environment directory is. If the <code class="literal">-h</code>
|
|||
|
option is not provided when this example is run, the current working
|
|||
|
directory is used instead.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Parse the command line arguments
|
|||
|
#ifdef _WIN32
|
|||
|
dbHomeDir = ".\\";
|
|||
|
#else
|
|||
|
dbHomeDir = "./";
|
|||
|
#endif
|
|||
|
while ((ch = getopt(argc, argv, "h:")) != EOF)
|
|||
|
switch (ch) {
|
|||
|
case 'h':
|
|||
|
dbHomeDir = optarg;
|
|||
|
break;
|
|||
|
case '?':
|
|||
|
default:
|
|||
|
return (usage());
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Next we create our database handle, and we define our environment open flags.
|
|||
|
There are a few things to notice here:
|
|||
|
</p>
|
|||
|
<div class="itemizedlist">
|
|||
|
<ul type="disc">
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We specify <code class="literal">DB_RECOVER</code>, which means that normal
|
|||
|
recovery is run every time we start the application. This is
|
|||
|
highly desirable and recommended for most
|
|||
|
applications.
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We also specify <code class="literal">DB_THREAD</code>, which means our
|
|||
|
environment handle will be free-threaded. This is very
|
|||
|
important because we will be sharing the environment handle
|
|||
|
across threads.
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
<pre class="programlisting"> // Env open flags
|
|||
|
envFlags =
|
|||
|
DB_CREATE | // Create the environment if it does not exist
|
|||
|
DB_RECOVER | // Run normal recovery.
|
|||
|
DB_INIT_LOCK | // Initialize the locking subsystem
|
|||
|
DB_INIT_LOG | // Initialize the logging subsystem
|
|||
|
DB_INIT_TXN | // Initialize the transactional subsystem. This
|
|||
|
// also turns on logging.
|
|||
|
DB_INIT_MPOOL | // Initialize the memory pool (in-memory cache)
|
|||
|
DB_THREAD; // Cause the environment to be free-threaded
|
|||
|
|
|||
|
try {
|
|||
|
// Create and open the environment
|
|||
|
envp = new DbEnv(0); </pre>
|
|||
|
<p>
|
|||
|
Now we configure how we want deadlock detection performed. In our case, we will cause DB to perform deadlock
|
|||
|
detection by walking its internal lock tables looking for a block every time a lock is requested. Further, in the
|
|||
|
event of a deadlock, the thread that holds the youngest lock will receive the deadlock notification.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Indicate that we want db to internally perform deadlock
|
|||
|
// detection. Also indicate that the transaction with
|
|||
|
// the fewest number of write locks will receive the
|
|||
|
// deadlock notification in the event of a deadlock.
|
|||
|
envp->set_lk_detect(DB_LOCK_MINWRITE); </pre>
|
|||
|
<p>
|
|||
|
Now we open our environment.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // If we had utility threads (for running checkpoints or
|
|||
|
// deadlock detection, for example) we would spawn those
|
|||
|
// here. However, for a simple example such as this,
|
|||
|
// that is not required.
|
|||
|
|
|||
|
envp->open(dbHomeDir, envFlags, 0); </pre>
|
|||
|
<p>
|
|||
|
Now we call the function that will open our database for us. This is
|
|||
|
not very interesting, except that you will notice that we are
|
|||
|
specifying <code class="literal">DB_DUPSORT</code>. This is required purely by
|
|||
|
the data that we are writing to the database, and it is only necessary
|
|||
|
if you run the application more than once without first deleting the environment.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The implementation of <code class="function">open_db()</code> is described
|
|||
|
later in this section.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Open the database
|
|||
|
openDb(&dbp, progName, fileName, envp, DB_DUPSORT); </pre>
|
|||
|
<p>
|
|||
|
Now we create our threads. In this example we are using pthreads
|
|||
|
for our threading package. A description of threading (beyond how
|
|||
|
it impacts DB usage) is beyond the scope of this manual.
|
|||
|
However, the things that we are doing here should be familiar to
|
|||
|
anyone who has prior experience with any threading package. We are
|
|||
|
simply initializing a mutex, creating our threads, and then joining
|
|||
|
our threads, which causes our program to wait until the joined
|
|||
|
threads have completed before continuing operations in the main
|
|||
|
thread.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Initialize a pthread mutex. Used to help provide thread ids.
|
|||
|
(void)pthread_mutex_init(&thread_num_lock, NULL);
|
|||
|
|
|||
|
// Start the writer threads.
|
|||
|
for (i = 0; i < NUMWRITERS; i++)
|
|||
|
(void)pthread_create(&writerThreads[i], NULL,
|
|||
|
writerThread, (void *)dbp);
|
|||
|
|
|||
|
// Join the writers
|
|||
|
for (i = 0; i < NUMWRITERS; i++)
|
|||
|
(void)pthread_join(writerThreads[i], NULL);
|
|||
|
|
|||
|
} catch(DbException &e) {
|
|||
|
std::cerr << "Error opening database environment: "
|
|||
|
<< dbHomeDir << std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
return (EXIT_FAILURE);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Finally, to wrap up <code class="function">main()</code>, we close out our
|
|||
|
database and environment handle, as is normal for any DB
|
|||
|
application. Notice that this is where our <code class="literal">err</code>
|
|||
|
label is placed in our application. If any database operation prior
|
|||
|
to this point in the program returns an error status, the program
|
|||
|
simply jumps to this point and closes our handles if necessary
|
|||
|
before exiting the application completely.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> try {
|
|||
|
// Close our database handle if it was opened.
|
|||
|
if (dbp != NULL)
|
|||
|
dbp->close(0);
|
|||
|
|
|||
|
// Close our environment if it was opened.
|
|||
|
if (envp != NULL)
|
|||
|
envp->close(0);
|
|||
|
} catch(DbException &e) {
|
|||
|
std::cerr << "Error closing database and environment."
|
|||
|
<< std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
return (EXIT_FAILURE);
|
|||
|
}
|
|||
|
|
|||
|
// Final status message and return.
|
|||
|
|
|||
|
std::cout << "I'm all done." << std::endl;
|
|||
|
return (EXIT_SUCCESS);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Now that we have completed <code class="function">main()</code>, we need to
|
|||
|
implement the function that our writer threads will actually run. This
|
|||
|
is where the bulk of our transactional code resides.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
We start as usual with variable declarations and initialization.
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// A function that performs a series of writes to a
|
|||
|
// Berkeley DB database. The information written
|
|||
|
// to the database is largely nonsensical, but the
|
|||
|
// mechanisms of transactional commit/abort and
|
|||
|
// deadlock detection are illustrated here.
|
|||
|
void *
|
|||
|
writerThread(void *args)
|
|||
|
{
|
|||
|
int j, thread_num;
|
|||
|
int max_retries = 20; // Max retry on a deadlock
|
|||
|
char *key_strings[] = {"key 1", "key 2", "key 3", "key 4",
|
|||
|
"key 5", "key 6", "key 7", "key 8",
|
|||
|
"key 9", "key 10"};
|
|||
|
|
|||
|
Db *dbp = (Db *)args;
|
|||
|
DbEnv *envp = dbp->get_env(); </pre>
|
|||
|
<p>
|
|||
|
Now we want a thread number for reporting purposes. It is possible to
|
|||
|
use the <code class="literal">pthread_t</code> value directly for this purpose,
|
|||
|
but how that is done unfortunately differs depending
|
|||
|
on the pthread implementation you are using. So instead we use a
|
|||
|
mutex-protected global variable to obtain a simple integer for
|
|||
|
our reporting purposes.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Note that we are also use this thread id for initializing a random number generator, which we do here.
|
|||
|
We use this random number generator for data generation.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Get the thread number
|
|||
|
(void)pthread_mutex_lock(&thread_num_lock);
|
|||
|
global_thread_num++;
|
|||
|
thread_num = global_thread_num;
|
|||
|
(void)pthread_mutex_unlock(&thread_num_lock);
|
|||
|
|
|||
|
// Initialize the random number generator
|
|||
|
srand((u_int)pthread_self()); </pre>
|
|||
|
<p>
|
|||
|
Now we begin the loop that we use to write data to the database.
|
|||
|
|
|||
|
<span>
|
|||
|
Notice that in this top loop, we begin a new transaction.
|
|||
|
</span>
|
|||
|
|
|||
|
We will actually use 50 transactions per writer
|
|||
|
thread, although we will only ever have one active transaction per
|
|||
|
thread at a time. Within each transaction, we will perform 10
|
|||
|
database writes.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
By combining multiple writes together under a single transaction,
|
|||
|
we increase the likelihood that a deadlock will occur. Normally,
|
|||
|
you want to reduce the potential for a deadlock and in this case
|
|||
|
the way to do that is to perform a single write per transaction.
|
|||
|
To avoid deadlocks, we could be using auto commit to
|
|||
|
write to our database for this workload.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
However, we want to show deadlock handling and by performing
|
|||
|
multiple writes per transaction we can actually observe deadlocks
|
|||
|
occurring. We also want to underscore the idea that you can
|
|||
|
combing multiple database operations together in a single atomic
|
|||
|
unit of work in order to improve the efficiency of your writes.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Perform 50 transactions
|
|||
|
for (int i=0; i<50; i++) {
|
|||
|
DbTxn *txn;
|
|||
|
bool retry = true;
|
|||
|
int retry_count = 0;
|
|||
|
// while loop is used for deadlock retries
|
|||
|
while (retry) {
|
|||
|
// try block used for deadlock detection and
|
|||
|
// general db exception handling
|
|||
|
try {
|
|||
|
|
|||
|
// Begin our transaction. We group multiple writes in
|
|||
|
// this thread under a single transaction so as to
|
|||
|
// (1) show that you can atomically perform multiple
|
|||
|
// writes at a time, and (2) to increase the chances
|
|||
|
// of a deadlock occurring so that we can observe our
|
|||
|
// deadlock detection at work.
|
|||
|
|
|||
|
// Normally we would want to avoid the potential for
|
|||
|
// deadlocks, so for this workload the correct thing
|
|||
|
// would be to perform our puts with auto commit. But
|
|||
|
// that would excessively simplify our example, so we
|
|||
|
// do the "wrong" thing here instead.
|
|||
|
txn = NULL;
|
|||
|
envp->txn_begin(NULL, &txn, 0); </pre>
|
|||
|
<p>
|
|||
|
Now we begin the inner loop that we use to actually
|
|||
|
perform the write.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // Perform the database write for this transaction.
|
|||
|
for (j = 0; j < 10; j++) {
|
|||
|
Dbt key, value;
|
|||
|
key.set_data(key_strings[j]);
|
|||
|
key.set_size((strlen(key_strings[j]) + 1) *
|
|||
|
sizeof(char));
|
|||
|
|
|||
|
int payload = rand() + i;
|
|||
|
value.set_data(&payload);
|
|||
|
value.set_size(sizeof(int));
|
|||
|
|
|||
|
// Perform the database put
|
|||
|
dbp->put(txn, &key, &value, 0);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Having completed the inner database write loop, we could simply
|
|||
|
commit the transaction and continue on to the next block of 10
|
|||
|
writes. However, we want to first illustrate a few points about
|
|||
|
transactional processing so instead we call our
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
|
|||
|
function before calling the transaction
|
|||
|
commit.
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
uses a cursor to read every
|
|||
|
record in the database and return a count of the number of records
|
|||
|
that it found.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> // countRecords runs a cursor over the entire database.
|
|||
|
// We do this to illustrate issues of deadlocking
|
|||
|
std::cout << thread_num << " : Found "
|
|||
|
<< countRecords(dbp, NULL)
|
|||
|
<< " records in the database." << std::endl;
|
|||
|
|
|||
|
std::cout << thread_num << " : committing txn : " << i
|
|||
|
<< std::endl;
|
|||
|
|
|||
|
// commit
|
|||
|
try {
|
|||
|
txn->commit(0);
|
|||
|
retry = false;
|
|||
|
txn = NULL;
|
|||
|
} catch (DbException &e) {
|
|||
|
std::cout << "Error on txn commit: "
|
|||
|
<< e.what() << std::endl;
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Finally, we finish our try block. Notice how we examine the
|
|||
|
exceptions to determine whether we need to
|
|||
|
abort (or abort/retry in the case of a deadlock) our current
|
|||
|
transaction.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> } catch (DbDeadlockException &de) {
|
|||
|
// First thing we MUST do is abort the transaction.
|
|||
|
if (txn != NULL)
|
|||
|
(void)txn->abort();
|
|||
|
|
|||
|
// Now we decide if we want to retry the operation.
|
|||
|
// If we have retried less than max_retries,
|
|||
|
// increment the retry count and goto retry.
|
|||
|
if (retry_count < max_retries) {
|
|||
|
std::cout << "############### Writer " << thread_num
|
|||
|
<< ": Got DB_LOCK_DEADLOCK.\n"
|
|||
|
<< "Retrying write operation."
|
|||
|
<< std::endl;
|
|||
|
retry_count++;
|
|||
|
retry = true;
|
|||
|
} else {
|
|||
|
// Otherwise, just give up.
|
|||
|
std::cerr << "Writer " << thread_num
|
|||
|
<< ": Got DeadLockException and out of "
|
|||
|
<< "retries. Giving up." << std::endl;
|
|||
|
retry = false;
|
|||
|
}
|
|||
|
} catch (DbException &e) {
|
|||
|
std::cerr << "db put failed" << std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
if (txn != NULL)
|
|||
|
txn->abort();
|
|||
|
retry = false;
|
|||
|
} catch (std::exception &ee) {
|
|||
|
std::cerr << "Unknown exception: " << ee.what() << std::endl;
|
|||
|
return (0);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return (0);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
<span>
|
|||
|
We want to back up for a moment and take a look at the call to <code class="function">countRecords()</code>.
|
|||
|
</span>
|
|||
|
If you look at the
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
function prototype at the beginning of this example, you will see that the
|
|||
|
function's second parameter takes a transaction handle. However,
|
|||
|
our usage of the function here does not pass a transaction handle
|
|||
|
through to the function.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
|
|||
|
Because
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
reads every record in the database, if used incorrectly the thread
|
|||
|
will self-deadlock. The writer thread has just written 500 records
|
|||
|
to the database, but because the transaction used for that write
|
|||
|
has not yet been committed, each of those 500 records are still
|
|||
|
locked by the thread's transaction. If we then simply run a
|
|||
|
non-transactional cursor over the database from within the same
|
|||
|
thread that has locked those 500 records, the cursor will
|
|||
|
block when it tries to read one of those transactional
|
|||
|
protected records. The thread immediately stops operation at that
|
|||
|
point while the cursor waits for the read lock it has
|
|||
|
requested. Because that read lock will never be released (the thread
|
|||
|
can never make any forward progress), this represents a
|
|||
|
self-deadlock for the the thread.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
There are three ways to prevent this self-deadlock:
|
|||
|
</p>
|
|||
|
<div class="orderedlist">
|
|||
|
<ol type="1">
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We can move the call to
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
to a point after the thread's transaction has committed.
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We can allow
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
to operate under the same transaction as all of the writes
|
|||
|
were performed (this is what the transaction parameter for
|
|||
|
the function is for).
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We can reduce our isolation guarantee for the application
|
|||
|
by allowing uncommitted reads.
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
</ol>
|
|||
|
</div>
|
|||
|
<p>
|
|||
|
For this example, we choose to use option 3 (uncommitted reads) to avoid
|
|||
|
the deadlock. This means that we have to open our database such
|
|||
|
that it supports uncommitted reads, and we have to open our cursor handle
|
|||
|
so that it knows to perform uncommitted reads.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
Note that in <a class="xref" href="inmem_txnexample_c.html" title="In-Memory Transaction Example">In-Memory Transaction Example</a>,
|
|||
|
we simply perform the cursor operation using the same transaction
|
|||
|
as is used for the thread's writes.
|
|||
|
</p>
|
|||
|
<p>
|
|||
|
The following is the
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
implementation. There is not anything particularly interesting
|
|||
|
about this function other than specifying uncommitted reads when
|
|||
|
we open the cursor handle, but we include the function here anyway
|
|||
|
for the sake of completeness.
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// This simply counts the number of records contained in the
|
|||
|
// database and returns the result.
|
|||
|
//
|
|||
|
// Note that this method exists only for illustrative purposes.
|
|||
|
// A more straight-forward way to count the number of records in
|
|||
|
// a database is to use the Database.getStats() method.
|
|||
|
int
|
|||
|
countRecords(Db *dbp, DbTxn *txn)
|
|||
|
{
|
|||
|
|
|||
|
Dbc *cursorp = NULL;
|
|||
|
int count = 0;
|
|||
|
|
|||
|
try {
|
|||
|
// Get the cursor
|
|||
|
dbp->cursor(txn, &cursorp, DB_READ_UNCOMMITTED);
|
|||
|
|
|||
|
Dbt key, value;
|
|||
|
while (cursorp->get(&key, &value, DB_NEXT) == 0) {
|
|||
|
count++;
|
|||
|
}
|
|||
|
} catch (DbDeadlockException &de) {
|
|||
|
std::cerr << "countRecords: got deadlock" << std::endl;
|
|||
|
cursorp->close();
|
|||
|
throw de;
|
|||
|
} catch (DbException &e) {
|
|||
|
std::cerr << "countRecords error:" << std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
}
|
|||
|
|
|||
|
if (cursorp != NULL) {
|
|||
|
try {
|
|||
|
cursorp->close();
|
|||
|
} catch (DbException &e) {
|
|||
|
std::cerr << "countRecords: cursor close failed:" << std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (count);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Finally, we provide the implementation of our
|
|||
|
|
|||
|
<code class="function">openDb()</code>
|
|||
|
function. This function should hold
|
|||
|
no surprises for you. Note, however, that we do specify uncommitted reads
|
|||
|
when we open the database. If we did not do this, then our
|
|||
|
|
|||
|
<code class="function">countRecords()</code>
|
|||
|
function would cause our
|
|||
|
thread to self-deadlock because the cursor could not be opened to
|
|||
|
support uncommitted reads (that flag on the cursor open would, in fact,
|
|||
|
be silently ignored by DB).
|
|||
|
</p>
|
|||
|
<pre class="programlisting">// Open a Berkeley DB database
|
|||
|
int
|
|||
|
openDb(Db **dbpp, const char *progname, const char *fileName,
|
|||
|
DbEnv *envp, u_int32_t extraFlags)
|
|||
|
{
|
|||
|
int ret;
|
|||
|
u_int32_t openFlags;
|
|||
|
|
|||
|
try {
|
|||
|
Db *dbp = new Db(envp, 0);
|
|||
|
|
|||
|
// Point to the new'd Db
|
|||
|
*dbpp = dbp;
|
|||
|
|
|||
|
if (extraFlags != 0)
|
|||
|
ret = dbp->set_flags(extraFlags);
|
|||
|
|
|||
|
// Now open the database
|
|||
|
openFlags = DB_CREATE | // Allow database creation
|
|||
|
DB_READ_UNCOMMITTED | // Allow uncommitted reads
|
|||
|
DB_AUTO_COMMIT | /* Allow auto commit */
|
|||
|
DB_THREAD; /* Cause the database to
|
|||
|
be free-threaded */
|
|||
|
|
|||
|
dbp->open(NULL, // Txn pointer
|
|||
|
fileName, // File name
|
|||
|
NULL, // Logical db name
|
|||
|
DB_BTREE, // Database type (using btree)
|
|||
|
openFlags, // Open flags
|
|||
|
0); // File mode. Using defaults
|
|||
|
} catch (DbException &e) {
|
|||
|
std::cerr << progname << "open_db: db open failed:" << std::endl;
|
|||
|
std::cerr << e.what() << std::endl;
|
|||
|
return (EXIT_FAILURE);
|
|||
|
}
|
|||
|
|
|||
|
return (EXIT_SUCCESS);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
This completes our transactional example. If you would like to
|
|||
|
experiment with this code, you can find the example in the following
|
|||
|
location in your DB distribution:
|
|||
|
</p>
|
|||
|
<pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_cxx/txn_guide</pre>
|
|||
|
</div>
|
|||
|
<div class="navfooter">
|
|||
|
<hr />
|
|||
|
<table width="100%" summary="Navigation footer">
|
|||
|
<tr>
|
|||
|
<td width="40%" align="left"><a accesskey="p" href="wrapup.html">Prev</a> </td>
|
|||
|
<td width="20%" align="center">
|
|||
|
<a accesskey="u" href="wrapup.html">Up</a>
|
|||
|
</td>
|
|||
|
<td width="40%" align="right"> <a accesskey="n" href="inmem_txnexample_c.html">Next</a></td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td width="40%" align="left" valign="top">Chapter 6. Summary and Examples </td>
|
|||
|
<td width="20%" align="center">
|
|||
|
<a accesskey="h" href="index.html">Home</a>
|
|||
|
</td>
|
|||
|
<td width="40%" align="right" valign="top"> In-Memory Transaction Example</td>
|
|||
|
</tr>
|
|||
|
</table>
|
|||
|
</div>
|
|||
|
</body>
|
|||
|
</html>
|