|
|
||||||||||||||||||||
| Home > Improving ODBC application performance and coding | |
| Chapter Download: |
|
||
Table of Contents
ODBC Applications: Writing Good Code Developing performance-optimized ODBC applications is not easy. Microsoft's ODBC Programmer's Reference does not provide information about performance. In addition, ODBC drivers and the ODBC Driver Manager don't return warnings when applications run inefficiently. This chapter describes some general guidelines for coding practices that improve ODBC application performance. These guidelines have been compiled by examining the ODBC implementations of numerous shipping ODBC applications. In general, the guidelines described in this chapter improve performance because they accomplish one or more of the following goals:
If you've read the other coding chapters (Chapters 6 and 7), you'll notice that some of the information here resembles those chapters. While there are some similarities, this chapter focuses on specific information about coding for ODBC.
Typically, creating a connection is one of the most performance-expensive operations that an application performs. Developers often assume that establishing a connection is a simple request that results in the driver making a single network round trip to the database server to validate a user's credentials. In reality, a connection involves many network round trips between the driver and the database server. For example, when a driver connects to Oracle or Sybase ASE, that connection may require seven to ten network round trips. In addition, the database establishes resources on behalf of the connection, which involves performance expensive disk I/O and memory allocation. Your time will be well spent if you sit down and design how to handle connections before implementing them in your application. Use the guidelines in this section to manage your connections more efficiently.
Connecting Efficiently
When choosing a method to manage connections, remember the following facts about connections and performance:
Using Connection Pooling Although obtaining connections from a pool is efficient, when your application opens and closes connections impacts the scalability of your application. Open connections just before the user needs them, not sooner, to minimize the time that the user owns the physical connection. Similarly, close connections as soon as the user no longer needs them. To minimize the number of connections required in a connection pool to service users, you can switch a user associated with a connection to another user if your database driver supports a feature known as reauthentication. Minimizing the number of connections conserves memory and can improve performance. See "Using Reauthentication with Connection Pooling," page 232. See Chapter 8, "Connection Pooling and Statement Pooling," for details about connection pooling.
Some applications are not good candidates for using connection pooling, particularly if connection reuse is limited. See "When Not to Use Connection Pooling," page 15, for examples.
Using One Connection for Multiple Statements In contrast, when connecting to cursor-based protocol databases, the driver tells the database server when to work and how much data to retrieve. Several cursors can use the network, each working in small slices of time. Oracle and DB2 are examples of cursor-based protocol databases. For a more detailed explanation of streaming versus cursor-based protocol databases, see "One Connection for Multiple Statements," page 16. The advantage of using one connection for multiple statements is that it reduces the overhead of establishing multiple connections, while allowing multiple statements to access the database. The overhead is reduced on both the database server and client machines. The disadvantage is that the application may have to wait to execute a statement until the single connection is available. See "One Connection for Multiple Statements," page 16, for guidelines on using this model of connection management.
Remember that creating a connection is one of the most performance-expensive operations that an application performs. How often do databases change their supported data types or database version between connections? Because this type of information typically doesn't change between connections and isn't a large amount of information to store, you may want to retrieve and cache the information so the application can access it later. Managing Transactions To ensure data integrity, all statements in a transaction are committed or rolled back as a unit. For example, when you use a computer to transfer money from one bank account to another, the request involves a transaction—updating values stored in the database for both accounts. If all parts of that unit of work succeed, the transaction is committed. If any part of that unit of work fails, the transaction is rolled back. Use the guidelines in this section to help you manage transactions more efficiently. Managing Commits in Transactions Committing (and rolling back) transactions is slow because of the disk I/O and, potentially, the number of network round trips required. What does a commit actually involve? The database must write to disk every modification made by a transaction to the database. This is usually a sequential write to a journal file (or log); nevertheless, it involves expensive disk I/O. In ODBC, the default transaction commit mode is auto-commit. In autocommit mode, a commit is performed for every SQL statement that requires a request to the database (Insert, Update, Delete, and Select statements).When auto-commit mode is used, your application doesn't control when database work is committed. In fact, commits commonly occur when there's actually no real work to commit. Some databases, such as DB2, don't support auto-commit mode. For these databases, the database driver, by default, sends a commit request to the database after every successful operation (SQL statement). The commit request equates to a network round trip between the driver and the database. The round trip to the database occurs even though the application didn't request the commit and even if the operation made no changes to the database. For example, the driver makes a network round trip even when a Select statement is executed. Let's look at the following ODBC code, which doesn't turn off auto-commit mode. Comments in the code show when commits occur if the driver or the database performs commits automatically: /* For conciseness, this code omits error checking */
/* Allocate a statement handle */
/* Bind parameters */
/* Set parameter values before execution */ /* A commit occurs because auto-commit is on */
/* Change parameter values for the next execution */ /* A commit occurs because auto-commit is on */
/* Reset parameter bindings */
/* Execute a SELECT statement. A prepare is unnecessary
/* Fetch the first row */
/* All rows are returned when fetch
/* Get the data for each column in the result set row */
/* Fetch the next row of data */
/* Close the cursor */
/* Whether a commit occurs after a SELECT statement
/* Prepare the UPDATE statement for multiple executions */ rc = SQLPrepare ((SQLHSTMT)hstmt, sqlStatement, SQL_NTS);
/* Bind parameter */ for (index = 0; index < 10; index++) {
/* Execute the UPDATE statement for each
/* Because auto-commit is on, a commit occurs each time
/* Reset parameter bindings */
/* Execute a SELECT statement. A prepare is unnecessary
strcpy(sqlStatement, "SELECT id, name, salary" +
/* Fetch the first row */
/* All rows are returned when fetch
/* Fetch the next row of data */
/* Close the cursor */
/* Whether a commit occurs after a SELECT statement
/* For conciseness, this code omits error checking */
/* Allocate a statement handle */
/* Turn auto-commit off */
/* Prepare an INSERT statement for multiple executions */
/* Bind parameters */
/* Set parameter values before execution */
/* Change parameter values for the next execution */
/* Reset parameter bindings */
/* Manual commit */
/* Execute a SELECT statement. A prepare is unnecessary
/* Fetch the first row */
/* All rows are returned when fetch
/* Get the data for each column in the result set row */
/* Fetch the next row of data */
/* Close the cursor */
strcpy (sqlStatement,
/* Prepare the UPDATE statement for multiple executions */
/* Bind parameter */ for (index = 0; index < 10; index++) {
/* Execute the UPDATE statement for each
/* Manual commit */
/* Reset parameter bindings */
/* Execute a SELECT statement. A prepare is unnecessary
/* Fetch the first row */
/* All rows are returned when fetch
/* Get the data for each column in the result set row */
/* Fetch the next row of data */
/* Close the cursor */
/* Manual commit */ See "Managing Commits in Transactions," page 22, for information on when to commit work if you've turned off auto-commit mode.
Which type of transaction should you use: local or distributed? A local transaction accesses and updates data on a single database. A distributed transaction accesses and updates data on multiple databases; therefore, it must be coordinated among those databases. Be aware that the default transaction behavior of many COM+ components uses distributed transactions, so changing that default transaction behavior to local transactions as shown can improve performance.
// Disable MTS Transactions. See "Transaction Management," page 21, for more information about performance and transactions. More on accessing data:
'); // -->
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| About Us | Contact Us | For Advertisers | For Business Partners | Site Index | RSS |
|
|
|
|||||||