mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
762 lines
30 KiB
HTML
762 lines
30 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: txn_guide.c */
|
|||
|
|
|||
|
/* We assume an ANSI-compatible compiler */
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <string.h>
|
|||
|
#include <pthread.h>
|
|||
|
#include <db.h>
|
|||
|
|
|||
|
#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 count_records(DB *, DB_TXN *);
|
|||
|
int open_db(DB **, const char *, const char *, DB_ENV *, u_int32_t);
|
|||
|
int usage(void);
|
|||
|
void *writer_thread(void *); </pre>
|
|||
|
<p>
|
|||
|
We now implement our usage function, which identifies our only command line
|
|||
|
parameter:
|
|||
|
</p>
|
|||
|
<pre class="programlisting">/* Usage function */
|
|||
|
int
|
|||
|
usage()
|
|||
|
{
|
|||
|
fprintf(stderr, " [-h <database_home_directory>]\n");
|
|||
|
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;
|
|||
|
DB_ENV *envp = NULL;
|
|||
|
|
|||
|
pthread_t writer_threads[NUMWRITERS];
|
|||
|
int ch, i, ret, ret_t;
|
|||
|
u_int32_t env_flags;
|
|||
|
char *db_home_dir;
|
|||
|
/* Application name */
|
|||
|
const char *prog_name = "txn_guide";
|
|||
|
/* Database file name */
|
|||
|
const char *file_name = "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
|
|||
|
db_home_dir = ".\\";
|
|||
|
#else
|
|||
|
db_home_dir = "./";
|
|||
|
#endif
|
|||
|
while ((ch = getopt(argc, argv, "h:")) != EOF)
|
|||
|
switch (ch) {
|
|||
|
case 'h':
|
|||
|
db_home_dir = 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"> /* Create the environment */
|
|||
|
ret = db_env_create(&envp, 0);
|
|||
|
if (ret != 0) {
|
|||
|
fprintf(stderr, "Error creating environment handle: %s\n",
|
|||
|
db_strerror(ret));
|
|||
|
goto err;
|
|||
|
}
|
|||
|
|
|||
|
env_flags =
|
|||
|
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 */ </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>
|
|||
|
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">
|
|||
|
<h3 class="title">Note</h3>
|
|||
|
<p>
|
|||
|
You will notice that every database operation checks the
|
|||
|
operation's status return code, and if an error
|
|||
|
(non-zero) status is returned, we log the error and then go to
|
|||
|
a <code class="literal">err</code> label in our program. Unlike
|
|||
|
object-oriented programs such as C++ or Java, we do not have
|
|||
|
<code class="literal">try</code> blocks in C. Therefore, this is the best
|
|||
|
way for us to implement cascading error handling for this
|
|||
|
example.
|
|||
|
</p>
|
|||
|
</div>
|
|||
|
<pre class="programlisting"> /*
|
|||
|
* Indicate that we want db to perform lock detection internally.
|
|||
|
* Also indicate that the transaction with the fewest number of
|
|||
|
* write locks will receive the deadlock notification in
|
|||
|
* the event of a deadlock.
|
|||
|
*/
|
|||
|
ret = envp->set_lk_detect(envp, DB_LOCK_MINWRITE);
|
|||
|
if (ret != 0) {
|
|||
|
fprintf(stderr, "Error setting lock detect: %s\n",
|
|||
|
db_strerror(ret));
|
|||
|
goto err;
|
|||
|
} </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.
|
|||
|
*/
|
|||
|
|
|||
|
/* Now actually open the environment */
|
|||
|
ret = envp->open(envp, db_home_dir, env_flags, 0);
|
|||
|
if (ret != 0) {
|
|||
|
fprintf(stderr, "Error opening environment: %s\n",
|
|||
|
db_strerror(ret));
|
|||
|
goto err;
|
|||
|
} </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>
|
|||
|
Also, we do not provide any error logging here because the
|
|||
|
<code class="function">open_db()</code> function does that for us.
|
|||
|
(The implementation of <code class="function">open_db()</code> is described
|
|||
|
later in this section.)
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> /* Open the database */
|
|||
|
ret = open_db(&dbp, prog_name, file_name, envp, DB_DUPSORT);
|
|||
|
if (ret != 0)
|
|||
|
goto err; </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(&writer_threads[i], NULL,
|
|||
|
writer_thread, (void *)dbp);
|
|||
|
|
|||
|
/* Join the writers */
|
|||
|
for (i = 0; i < NUMWRITERS; i++)
|
|||
|
(void)pthread_join(writer_threads[i], NULL); </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">err:
|
|||
|
/* Close our database handle, if it was opened. */
|
|||
|
if (dbp != NULL) {
|
|||
|
ret_t = dbp->close(dbp, 0);
|
|||
|
if (ret_t != 0) {
|
|||
|
fprintf(stderr, "%s database close failed: %s\n",
|
|||
|
file_name, db_strerror(ret_t));
|
|||
|
ret = ret_t;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Close our environment, if it was opened. */
|
|||
|
if (envp != NULL) {
|
|||
|
ret_t = envp->close(envp, 0);
|
|||
|
if (ret_t != 0) {
|
|||
|
fprintf(stderr, "environment close failed: %s\n",
|
|||
|
db_strerror(ret_t));
|
|||
|
ret = ret_t;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Final status message and return. */
|
|||
|
printf("I'm all done.\n");
|
|||
|
return (ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
|||
|
} </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 *
|
|||
|
writer_thread(void *args)
|
|||
|
{
|
|||
|
DBT key, value;
|
|||
|
DB_TXN *txn;
|
|||
|
int i, j, payload, ret, thread_num;
|
|||
|
int retry_count, 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;
|
|||
|
DB_ENV *envp = dbp->get_env(dbp); </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 at the beginning of the 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>
|
|||
|
<p>
|
|||
|
Finally, on an issue of style, you will notice the
|
|||
|
<code class="literal">retry</code> label that we place immediately before our
|
|||
|
transaction begin code. We use this to loop in the event that a
|
|||
|
deadlock is detected and the write operation has to be performed. A
|
|||
|
great many people dislike looping with <code class="literal">goto</code>
|
|||
|
statements, and we certainly could have written this code to avoid
|
|||
|
it. However, we find that using the
|
|||
|
<code class="literal">goto</code> in this case greatly helps to clarify the
|
|||
|
code, so we ignore the bias against <code class="literal">goto</code>
|
|||
|
programming in order to clearly support looping in the event of
|
|||
|
what is really an error condition.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> /* Write 50 times and then quit */
|
|||
|
for (i = 0; i < 50; i++) {
|
|||
|
retry_count = 0; /* Used for deadlock retries */
|
|||
|
|
|||
|
retry:
|
|||
|
/* Begin our transaction. */
|
|||
|
ret = envp->txn_begin(envp, NULL, &txn, 0);
|
|||
|
if (ret != 0) {
|
|||
|
envp->err(envp, ret, "txn_begin failed");
|
|||
|
return ((void *)EXIT_FAILURE);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Now we begin the inner loop that we use to actually
|
|||
|
perform the write. Notice that we use a <code class="literal">case</code>
|
|||
|
statement to examine the return code from the database put.
|
|||
|
This case statement is what we use to determine whether we need
|
|||
|
to abort (or abort/retry in the case of a deadlock) our current
|
|||
|
transaction.
|
|||
|
</p>
|
|||
|
<pre class="programlisting"> for (j = 0; j < 10; j++) {
|
|||
|
/* Set up our key and values DBTs */
|
|||
|
memset(&key, 0, sizeof(DBT));
|
|||
|
key.data = key_strings[j];
|
|||
|
key.size = (strlen(key_strings[j]) + 1) * sizeof(char);
|
|||
|
|
|||
|
memset(&value, 0, sizeof(DBT));
|
|||
|
payload = rand() + i;
|
|||
|
value.data = &payload;
|
|||
|
value.size = sizeof(int);
|
|||
|
|
|||
|
/* Perform the database put. */
|
|||
|
switch (ret = dbp->put(dbp, txn, &key, &value, 0)) {
|
|||
|
case 0:
|
|||
|
break;
|
|||
|
/*
|
|||
|
* Our database is configured for sorted duplicates,
|
|||
|
* so there is a potential for a KEYEXIST error return.
|
|||
|
* If we get one, simply ignore it and continue on.
|
|||
|
*
|
|||
|
* Note that you will see KEYEXIST errors only after you
|
|||
|
* have run this program at least once.
|
|||
|
*/
|
|||
|
case DB_KEYEXIST:
|
|||
|
printf("Got keyexists.\n");
|
|||
|
break;
|
|||
|
/*
|
|||
|
* Here's where we perform deadlock detection. If
|
|||
|
* DB_LOCK_DEADLOCK is returned by the put operation,
|
|||
|
* then this thread has been chosen to break a deadlock.
|
|||
|
* It must abort its operation, and optionally retry the
|
|||
|
* put.
|
|||
|
*/
|
|||
|
case DB_LOCK_DEADLOCK:
|
|||
|
/*
|
|||
|
* First thing we MUST do is abort the
|
|||
|
* transaction.
|
|||
|
*/
|
|||
|
(void)txn->abort(txn);
|
|||
|
/*
|
|||
|
* 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) {
|
|||
|
printf("Writer %i: Got DB_LOCK_DEADLOCK.\n",
|
|||
|
thread_num);
|
|||
|
printf("Writer %i: Retrying write operation.\n",
|
|||
|
thread_num);
|
|||
|
retry_count++;
|
|||
|
goto retry;
|
|||
|
}
|
|||
|
/*
|
|||
|
* Otherwise, just give up.
|
|||
|
*/
|
|||
|
printf("Writer %i: ", thread_num);
|
|||
|
printf("Got DB_LOCK_DEADLOCK and out of retries.\n");
|
|||
|
printf("Writer %i: Giving up.\n", thread_num);
|
|||
|
return ((void *)EXIT_FAILURE);
|
|||
|
/*
|
|||
|
* If a generic error occurs, we simply abort the
|
|||
|
* transaction and exit the thread completely.
|
|||
|
*/
|
|||
|
default:
|
|||
|
envp->err(envp, ret, "db put failed");
|
|||
|
ret = txn->abort(txn);
|
|||
|
if (ret != 0)
|
|||
|
envp->err(envp, ret, "txn abort failed");
|
|||
|
return ((void *)EXIT_FAILURE);
|
|||
|
} /** End case statement **/
|
|||
|
|
|||
|
} /** End for loop **/ </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">count_records()</code>
|
|||
|
|
|||
|
|
|||
|
function before calling the transaction
|
|||
|
commit.
|
|||
|
<code class="function">count_records()</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"> /*
|
|||
|
* print the number of records found in the database.
|
|||
|
* See count_records() for usage information.
|
|||
|
*/
|
|||
|
printf("Thread %i. Record count: %i\n", thread_num,
|
|||
|
count_records(dbp, NULL));
|
|||
|
|
|||
|
/*
|
|||
|
* If all goes well, we can commit the transaction and
|
|||
|
* loop to the next transaction.
|
|||
|
*/
|
|||
|
ret = txn->commit(txn, 0);
|
|||
|
if (ret != 0) {
|
|||
|
envp->err(envp, ret, "txn commit failed");
|
|||
|
return ((void *)EXIT_FAILURE);
|
|||
|
}
|
|||
|
}
|
|||
|
return ((void *)EXIT_SUCCESS);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
|
|||
|
If you look at the
|
|||
|
<code class="function">count_records()</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">count_records()</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">count_records()</code>
|
|||
|
|
|||
|
to a point after the thread's transaction has committed.
|
|||
|
</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>
|
|||
|
We can allow
|
|||
|
<code class="function">count_records()</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">count_records()</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 function exists only for illustrative purposes.
|
|||
|
* A more straight-forward way to count the number of records in
|
|||
|
* a database is to use DB->stat() or DB->stat_print().
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
count_records(DB *dbp, DB_TXN *txn)
|
|||
|
{
|
|||
|
DBT key, value;
|
|||
|
DBC *cursorp;
|
|||
|
int count, ret;
|
|||
|
|
|||
|
cursorp = NULL;
|
|||
|
count = 0;
|
|||
|
|
|||
|
/* Get the cursor */
|
|||
|
ret = dbp->cursor(dbp, txn, &cursorp, DB_READ_UNCOMMITTED);
|
|||
|
if (ret != 0) {
|
|||
|
dbp->err(dbp, ret, "count_records: cursor open failed.");
|
|||
|
goto cursor_err;
|
|||
|
}
|
|||
|
|
|||
|
/* Get the key DBT used for the database read */
|
|||
|
memset(&key, 0, sizeof(DBT));
|
|||
|
memset(&value, 0, sizeof(DBT));
|
|||
|
do {
|
|||
|
ret = cursorp->get(cursorp, &key, &value, DB_NEXT);
|
|||
|
switch (ret) {
|
|||
|
case 0:
|
|||
|
count++;
|
|||
|
break;
|
|||
|
case DB_NOTFOUND:
|
|||
|
break;
|
|||
|
default:
|
|||
|
dbp->err(dbp, ret, "Count records unspecified error");
|
|||
|
goto cursor_err;
|
|||
|
}
|
|||
|
} while (ret == 0);
|
|||
|
|
|||
|
cursor_err:
|
|||
|
if (cursorp != NULL) {
|
|||
|
ret = cursorp->close(cursorp);
|
|||
|
if (ret != 0) {
|
|||
|
dbp->err(dbp, ret,
|
|||
|
"count_records: cursor close failed.");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (count);
|
|||
|
} </pre>
|
|||
|
<p>
|
|||
|
Finally, we provide the implementation of our
|
|||
|
<code class="function">open_db()</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">count_records()</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
|
|||
|
open_db(DB **dbpp, const char *progname, const char *file_name,
|
|||
|
DB_ENV *envp, u_int32_t extra_flags)
|
|||
|
{
|
|||
|
int ret;
|
|||
|
u_int32_t open_flags;
|
|||
|
DB *dbp;
|
|||
|
|
|||
|
/* Initialize the DB handle */
|
|||
|
ret = db_create(&dbp, envp, 0);
|
|||
|
if (ret != 0) {
|
|||
|
fprintf(stderr, "%s: %s\n", progname,
|
|||
|
db_strerror(ret));
|
|||
|
return (EXIT_FAILURE);
|
|||
|
}
|
|||
|
|
|||
|
/* Point to the memory malloc'd by db_create() */
|
|||
|
*dbpp = dbp;
|
|||
|
|
|||
|
if (extra_flags != 0) {
|
|||
|
ret = dbp->set_flags(dbp, extra_flags);
|
|||
|
if (ret != 0) {
|
|||
|
dbp->err(dbp, ret,
|
|||
|
"open_db: Attempt to set extra flags failed.");
|
|||
|
return (EXIT_FAILURE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Now open the database */
|
|||
|
open_flags = 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 */
|
|||
|
|
|||
|
ret = dbp->open(dbp, /* Pointer to the database */
|
|||
|
NULL, /* Txn pointer */
|
|||
|
file_name, /* File name */
|
|||
|
NULL, /* Logical db name */
|
|||
|
DB_BTREE, /* Database type (using btree) */
|
|||
|
open_flags, /* Open flags */
|
|||
|
0); /* File mode. Using defaults */
|
|||
|
if (ret != 0) {
|
|||
|
dbp->err(dbp, ret, "Database '%s' open failed",
|
|||
|
file_name);
|
|||
|
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_c/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>
|