Programming hints and tips for CLI applications
These hints and tips can help you tune and improve the logic of your CLI applications.
- KEEPDYNAMIC support
- Common connection attributes
- Common statement attributes
- Reusing statement handles
- Binding and SQLGetData()
- Limiting use of catalog functions
- Column names of function generated result sets
- CLI-specific functions loaded from ODBC applications
- Global dynamic statement caching
- Data insertion and retrieval optimization
- Large object data optimization
- Case sensitivity of object identifiers
- SQLDriverConnect() versus SQLConnect()
- Turning off statement scanning
- Holding cursors across rollbacks
- Preparing compound SQL sub-statements
- User-defined types casting
- Deferred prepare to reduce network flow
The KEEPDYNAMIC behavior refers to the server's ability to keep a dynamic statement in a prepared state, even after a commit is performed. This behavior eliminates the need for the client to prepare the statement again, the next time the statement is executed. Some CLI/ODBC applications on the client might improve their performance by taking advantage of the KEEPDYNAMIC behavior on servers that are Db2® for z/OS® and OS/390® Version 7 and later.
- The application is using the packages that are bound with KEEPDYNAMIC YES.
- The Db2 for z/OS special register CURRENT APPLICATION COMPATIBILITY is set to V12R1.
- The dynamic sections exist.
- Enable the dynamic statement cache on the Db2 for z/OS and OS/390 server (see the Db2 for z/OS and OS/390 server documentation).
- Bind the db2clipk.bnd file on your Db2 client with the
KEEPDYNAMIC and COLLECTION options. The example shows how to bind db2clipk.bnd,
creating a collection named KEEPDYNC:
- db2 connect to database_name user userid using password
- db2 bind db2clipk.bnd SQLERROR CONTINUE BLOCKING ALL KEEPDYNAMIC YES COLLECTION KEEPDYNC GRANT PUBLIC
- db2 connect reset
- Inform the client that the KEEPDYNAMIC bind option is enabled for your
collection by performing either of the listed examples:
- Set the CLI/ODBC
configuration keywords in the db2cli.ini file: KeepDynamic =
1, CurrentPackageSet = collection name created in Step 2. For
example:
[dbname] KeepDynamic=1 CurrentPackageSet=KEEPDYNC
- Set the SQL_ATTR_KEEPDYNAMIC and SQL_ATTR_CURRENT_PACKAGE_SET connection attributes in the
CLI/ODBC
application. For example:
SQLSetConnectAttr(hDbc, SQL_ATTR_KEEP_DYNAMIC, (SQLPOINTER) 1, SQL_IS_UINTEGER ); SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_PACKAGE_SET, (SQLPOINTER) "KEEPDYNC", SQL_NTS);
- Set the CLI/ODBC
configuration keywords in the db2cli.ini file: KeepDynamic =
1, CurrentPackageSet = collection name created in Step 2. For
example:
Common connection attributes
- SQL_ATTR_AUTOCOMMIT - Generally this attribute should be set to
SQL_AUTOCOMMIT_OFF, because each commit request can generate extra
network flow. Only leave SQL_AUTOCOMMIT_ON on if specifically needed.
Note: The default is SQL_AUTOCOMMIT_ON.
- SQL_ATTR_TXN_ISOLATION - This connection attribute determines the isolation level at which the connection or statement will operate. The isolation level determines the level of concurrency possible, and the level of locking required to execute the statement. Applications must choose an isolation level that maximizes concurrency, yet ensures data consistency.
Common statement attributes
- SQL_ATTR_MAX_ROWS - Setting this attribute limits the number of rows returned to the application
from query operations. This can be used to avoid overwhelming an application with a very large
result set generated inadvertently, which is especially useful for applications on clients with
limited memory resources.
Setting SQL_ATTR_MAX_ROWS while connected to Db2 for z/OS Version 7 and later will add
OPTIMIZE FOR n ROWS
andFETCH FIRST n ROWS ONLY
clauses to the statement. For versions of Db2 for z/OS before Version 7 and any DBMS that does not support theFETCH FIRST n ROWS ONLY
clause, the full result set is still generated at the server using theOPTIMIZE FOR n ROWS
clause, however CLI will count the rows on the client and only fetch up to SQL_ATTR_MAX_ROWS rows. - SQL_ATTR_CURSOR_HOLD - This statement attribute determines if the cursor for this statement will
be declared by CLI using the WITH HOLD clause.
Resources associated with statement handles can be better utilized by the server if the statements that do not require cursor-hold behavior have this attribute set to SQL_CURSOR_HOLD_OFF. The efficiency gains obtained by the appropriate use of this attribute are considerable on OS/390 and Db2 for z/OS.
Note: Many ODBC applications expect a default behavior where the cursor position is maintained after a commit. - SQL_ATTR_TXN_ISOLATION - CLI allows
the isolation level to be set at the statement level, however, it
is recommended that the isolation level be set at the connection level.
The isolation level determines the level of concurrency possible,
and the level of locking required to execute the statement.
Resources associated with statement handles can be better utilized by CLI if statements are set to the required isolation level, rather than leaving all statements at the default isolation level. This should only be attempted with a thorough understanding of the locking and isolation levels of the connected DBMS.
Applications should use the minimum isolation level possible to maximize concurrency.
Reusing statement handles
SQLFreeStmt()
function.
Also, statement attributes previously set on a statement handle (for
example, SQL_ATTR_PARAMSET_SIZE) must be explicitly reset, otherwise
they might be inherited by all future statements using the statement
handle.Binding and SQLGetData()
SQLGetData()
.
When the data is in a LOB column, LOB functions are preferable to SQLGetData()
(see Large object data optimization for
more information). Use SQLGetData()
when the data
value is large variable-length data that: - must be received in pieces, or
- might not need to be retrieved.
Limiting use of catalog functions
Catalog
functions, such as SQLTables()
,
force the CLI driver
to query the DBMS catalog tables for information. The queries issued
are complex and the DBMS catalog tables can be very large. In general,
try to limit the number of times the catalog functions are called,
and limit the number of rows returned.
The number of catalog function calls can be reduced by calling the function once, and having the application store (cache) the data.
- Schema name or pattern for all catalog functions
- Table name or pattern for all catalog functions other than
SQLTables()
- Column name or pattern for catalog functions that return detailed column information.
Remember that although an application might be developed and tested against a data source with hundreds of tables, it might be run against a database with thousands of tables. Consider this likelihood when developing applications.
Close any open cursors
(call SQLCloseCursor()
or SQLFreeStmt()
with
SQL_CLOSE Option) for statement handles
used for catalog queries to release any locks against the catalog
tables. Outstanding locks on the catalog tables can prevent CREATE,
DROP or ALTER statements from executing.
Column names of function generated result sets
The column names of the result sets generated by catalog and information functions might change as the ODBC and CLI standards evolve. The position of the columns, however, will not change.
Any application dependency might be based on the column position (iCol parameter
used in SQLBindCol()
, SQLGetData()
,
and SQLDescribeCol()
)
and not the name.
CLI-specific functions loaded from ODBC applications
The ODBC Driver Manager maintains its own set of statement handles which it maps to the CLI statement handles on each call. When a CLI function is called directly, it must be passed to the CLI driver statement handle, as the CLI driver does not have access to the ODBC mapping.
Call SQLGetInfo()
with
the SQL_DRIVER_HSTMT option to obtain the CLI statement
handle (HSTMT). The CLI functions
can then be called directly from the shared library or DLL, passing
the HSTMT argument where required.
Global dynamic statement caching
Db2 servers at version 5 or later for UNIX or Windows have a global dynamic statement cache. This cache is used to store the most popular access plans for prepared dynamic SQL statements.
Before each statement is prepared, the server automatically searches this cache to see if an access plan has already been created for this exact SQL statement (by this application or any other application or client). If so, the server does not need to generate a new access plan, but will use the one in the cache instead. There is now no need for the application to cache connections at the client unless connecting to a server that does not have a global dynamic statement cache.
Data insertion and retrieval optimization
The methods that describe using arrays to bind parameters and retrieve data use compound SQL to optimize network flow. Use these methods as much as possible.
Large object data optimization
Use LOB data types and the supporting functions for long strings whenever
possible. Unlike LONG VARCHAR, LONG VARBINARY, and LONG VARGRAPHIC
types, LOB data values can use LOB locators and functions such as SQLGetPosition()
and SQLGetSubString()
to
manipulate large data values at the server.
LOB values can also be fetched directly to a file, and LOB parameter values can be read directly from a file. This saves the resource utilization of the application transferring data through application buffers.
Case sensitivity of object identifiers
All database object identifiers, such as table names, view names and column names are stored in the catalog tables in uppercase unless the identifier is delimited. If an identifier is created using a delimited name, the exact case of the name is stored in the catalog tables.
When an identifier is referenced within an SQL statement, it is treated as case insensitive unless it is delimited.
CREATE TABLE MyTable (id INTEGER)
CREATE TABLE "YourTable" (id INTEGER)
two tables will
exist, MYTABLE
and YourTable
SELECT * FROM MyTable (id INTEGER)
SELECT * FROM MYTABLE (id INTEGER)
TABLE NOT FOUND
because
there is no table named YOURTABLE
: SELECT * FROM "YourTable" (id INTEGER) // executes without error
SELECT * FROM YourTable (id INTEGER) // error, table not found
All CLI catalog function arguments treat the names of objects as case sensitive, that is, as if each name was delimited.
SQLDriverConnect() versus SQLConnect()
Using SQLDriverConnect()
allows the application to
rely on the dialog box provided by CLI to
prompt the user for the connection information.
If an application uses its own dialog boxes to query the connect information, the user might be able to specify additional connect options in the connection string. The string can also be stored and used as a default on subsequent connections.
Turning off statement scanning
CLI by default, scans each SQL statement searching for vendor escape clause sequences.
If the application does not generate SQL statements that contain vendor escape clause sequences, then the SQL_ATTR_NOSCAN statement attribute must be set to SQL_NOSCAN_ON at the connection level so that CLI does not perform a scan for vendor escape clauses.
Holding cursors across rollbacks
Applications that must deal with complex transaction management issues might benefit from establishing multiple concurrent connections to the same database. Each connection in CLI has its own transaction scope, so any actions performed on one connection do not affect the transactions of other connections.
For example, all open cursors within a transaction get closed if a problem causes the transaction to be rolled back. An application can use multiple connections to the same database to separate statements with open cursors; because the cursors are in separate transactions, a rollback on one statement does not affect the cursors of the other statements.
- Suppose in connection #1 you are accessing large object columns and have created LOB locators that map to portions of large object values.
- If in connection #2, you want to use (for example to insert) the portion of the LOB values represented by the LOB locators, you must move the LOB values in connection #1 first to the application, and then pass them to the tables that you are working with in connection #2. This is because connection #2 does not know anything about the LOB locators in connection #1.
- If you only had one connection, then you can just use the LOB locators directly. However, the LOB locators are lost as soon as you rolled back your transaction.
Preparing compound SQL sub-statements
In order to maximize efficiency of the compound statement, sub-statements might be prepared before the BEGIN COMPOUND statement, and then executed within the compound statement.
This also simplifies error handling because prepare errors can be handled outside of the compound statement.
User-defined types and casting
If a parameter marker is used in a predicate of a query statement, and the parameter is a user defined type, the statement must use a CAST function to cast either the parameter marker or the UDT.
CREATE DISTINCT TYPE CNUM AS INTEGER WITH COMPARISONS
CREATE TABLE CUSTOMER (
Cust_Num CNUM NOT NULL,
First_Name CHAR(30) NOT NULL,
Last_Name CHAR(30) NOT NULL,
Phone_Num CHAR(20) WITH DEFAULT,
PRIMARY KEY (Cust_Num) )
SELECT first_name, last_name, phone_num from customer
WHERE cust_num = ?
This statement fails because the
parameter marker cannot be of type CNUM and thus the comparison fails
due to incompatible types. SELECT first_name, last_name, phone_num from customer
where cast( cust_num as integer ) = ?
SELECT first_name, last_name, phone_num FROM customer
where cust_num = cast( ? as integer )
Deferred prepare to reduce network flow
SQLDescribeParam()
, SQLDescribeCol()
, SQLNumParams()
,
and SQLNumResultCols()
require
that the statement has been prepared. If the statement has not already
been prepared, these functions trigger an immediate PREPARE request
to the server, and the benefit of deferred prepare does not occur.