Using the QAQQINI File

The Query options file allows you to set defaults for how your SQL queries process on the iSeries, and it also allows you to set the types of messages you see in your job log. In this article, I will show you a technique for setting the query options without interfering with other programs on your system. These techniques can give you a greater degree of control over the implementation and optimization of your queries and better information from the iSeries job log with which to analyze performance problems. In addition, these settings can be used to trim the number of open data paths that your ODBC, JDBC and OLE DB programs keep open during operation. The information also applies to controlling OPNQRYF operations and Query/400 processing.

What is a QAQQINI?

Starting with V4R4, all of the various settings that can affect the query optimizer were placed into the QAQQINI file in the QSYS library. Before that, you had to use CHGQRYA, setting data areas like QQQOPTIONS or setting system values like query degree. The QAQQINI file is a physical file with attached triggers that controls how queries are processed on the iSeries. The file is quite an ingenious device; you make entries or updates to the file that describe aspects of how you want your queries to process, and the trigger processes the changes made to the file and propagates the changes to your job or other jobs that are using that query options file. Note that I said "your job or other jobs." A query options file can be placed in a library, and by issuing a Change Query Attributes command (CHGQRYA), you can tell your job to use that query options file. By default, jobs look for a QAQQINI file in the QUSRSYS library--but you can put a QAQQINI file in any library, even QTEMP.


The copy of the QAQQINI file in the QSYS library should be regarded as a template, and you should not make changes to the entries in that file. You need to make a copy of the file and place it in a user library; then, you can make changes to your hearts content. Since the QAQQINI file has triggers attached to it, you need to use the Create Duplicate Object (CRTDUPOBJ) command to make a copy. My favorite technique when debugging and playing with performance tuning is to make a copy of the QAQQINI file and place it into the QTEMP library. In that manner, I am assured that the changes that I am making to the QAQQINI file will not affect other users or jobs running on my system. Here is the syntax for creating a copy of the QAQQINI file into the QTEMP library:


The command above instructs the iSeries to create a duplicate copy, including data, of the file QAQQINI in the library QSYS and to place that duplicate into the QTEMP library. In order to make your job use the copy of the QAQQINI, you need to issue the Change Query Attributes command (CHGQRYA) to inform your job that it should be using this file. The syntax for the CHGQRYA command is:


The parameter QRYOPTLIB tells the iSeries which library it should look in to find the query options file that will control how this job is processed.


The QAQQINI file contains three columns, QQPARM, QQVAL, and QQTEXT. The QQPARM field contains the name of the optimizer parameter. The QQVAL field contains the current setting for the parameter named in QQPARM. QQTEXT is a description of the parameter. So if you want to set the query time limit for queries to 55 seconds, you could issue the following update statement against the QAQQINI file that your job is using:


What else can you change with the QAQQINI? Well, there are several options you can set, but the one that I use most is the ability to tell the iSeries to send any query optimizer messages to the job log. This is almost like using the Start Debug command on your job, but your job is not in debug mode. The advantage of using a setting in QAQQINI over throwing the job into debug mode is twofold: You do not have the additional overhead of debug mode, and you do not seen any messages in the job log other than messages appropriate to query optimization and performance. I find it much easier to read this way. To turn on query optimizer messages, issue the following update command from SQL:


Another thing you can control via QAQQINI it whether the iSeries is allowed to change the order of joins when optimizing a query. This is accomplished by setting the FORCE_JOIN_ORDER parameter value to *DEFAULT, *SQL, *NO, or *YES. By default, the iSeries tries to choose what it perceives to be the best order for processing files referenced in an SQL statement. You can override this behavior and cause the iSeries to force the processing of files in the order that they appear in the join clause or from clause by setting this parameter value to *YES. This can be particularly helpful when you are attempting to get the optimizer to choose a specific access path that it just doesn't seem to want to use. Setting the parameter to *SQL will cause the iSeries to only force the join order if the SQL statements use the join syntax. Note that this parameter does not ship in the default QAQQINI, do you will need to insert a record into QAQQINI before you can adjust the parameter. The following statement inserts a record into QAQQINI telling the optimizer to force the join order on all SQL statements to the order the statements are referenced in the join or from clause.


Another interesting parameter is OPTIMIZATION_GOAL, which allows you to set the goal that the query optimizer is trying to achieve. By default, the query optimizer will attempt to optimize a query for *FIRSTIO if the query is dynamic SQL or for *ALLIO if the query is static SQL. *FIRSTIO implies that the iSeries will attempt to retrieve and send the user rows that match the query selection requirement as soon as possible, whereas *ALLIO implies that the iSeries should attempt to run the entire query to its conclusion in the shortest amount of time. Note that if you have OPTIMIZE FOR n ROWS in your query, the query optimizer will ignore this setting. The OPTIMIZATION_GOAL parameter is not in the QAQQINI file that is shipped with V4R4, so you will need to insert a record into the QAQQINI file in order to be able to set this property. Once you have inserted a record, you may use an update statement to change the value of the parameter. The following statement inserts an option record for this parameter and sets its value to *FIRSTIO:


A really scary option is OPTIMIZE_STATISTIC_LIMITATION, which controls how deeply the iSeries will look at logical files that are attached to the physical files your query references. The optimizer will look at the actual key loading in logical files to determine how useful they will be in the resolution of a query. This key loading of and index is called itís selectivity. While the iSeries has some top-level statistics about an index available in the file system, those statistics do not tell the optimizer exactly how the expressions used in your query match up against the actual key values stored in the logical file. When the optimizer is trying to formulate an execution plan, it can elect to open the logical files that it thinks may help the query execute quickly and examine the key values in the logical file. The query optimizer automatically decides how much time that it will spend doing this process, but the OPTIMIZE_STATISTIC_LIMITATION value will let you override the built-in time limits and set your own metrics for the optimizer to use.

The valid settings for OPTIMIZE_STATISTIC_LIMITATION are *DEFAULT, *NO, *PERCENT n, where n is an integer, and *MAX_NUMBER_OF_RECORDS_ALLOWED n, where again n is an integer. The interesting options are *PERCENTAGE and *NO. *NO tells the optimizer to not look at records in the index at all and just go with the default metadata that is stored with the index. This can be good or bad, as the optimizer can make some stupid decisions if it has incomplete information. However, this option might help you if the optimizer is taking a stupid pill before optimizing your query, so it is something to keep in your arsenal of weapons. The *PERCENTAGE option is useful, as you can instruct to the optimizer on the maximum number of records to read when looking at index statistics. This can be useful when the optimizer is evaluating a large number of similar indexes and trying to determine which will be most suitable for the query. By setting the percentage high, you are instructing the optimizer to read no more than that percentage of entries in the index before finishing its statistical observations. This can help save time in queries against very large tables. The final option, *MAX_NUMBER_OF_RECORDS_ALLOWED, is like setting a threshold for the *NO option. This tells the optimizer not to look at index entries if there are more than N records in the base table. Again, if your queries are spending an inordinate amount of time in optimization due to a large number of logical files created against your physical files, this might be something to play with. The following statement sets the maximum percentage of a logical to read to 45 percent:


Open Data Paths

If you ever look at the open files of a QZDASOINIT job, you might be surprised at how many open data paths (ODPs) that the iSeries will keep around. ODPs are a good thing, as once the iSeries has opened the data path, the next time it needs it the machine will just reuse the open one (if possible). The problem with ODPs is that they do take a little RAM, and a large number of ODPs can have a negative impact on performance. The iSeries, by default, will never close these things down, at least not until your job ends. However, never fear! The QAQQINI is coming to your rescue. You can set the parameter OPEN_CURSOR_THRESHOLD to a number. This number is the maximum number of cursors associated with reusable ODPs that the iSeries should maintain. This parameter works hand-in-hand with the OPEN_CURSOR_CLOSE_COUNT parameter, which specifies how many open cursors to close once the threshold value is met. Note that the iSeries is not actually closing open cursors; it just closes cursors that you already closed in your application. In the interest of efficiency the iSeries is retaining them in a sort-of-open state to maintain the reusability of the ODP associated with cursor.

The OPEN_CURSOR_CLOSE_COUNT and OPEN_CURSOR_THRESHOLD are not supplied in the QAQQINI file available in QSYS, so you will need to insert the values into your copy of QSYS before you can begin updating and changing the values of these parameters. The following statements will tell the iSeries not to keep more than 30 ODPs and to release the oldest four when the threshold is reached:


QAQQINI in Production and Testing

So far, I've told you about QAQQINI and some of its wonderful settings, but how do you use it in a production and testing environment?

The code below shows a simple piece of Visual Basic code that uses ActiveX Data Object to copy a QAQQINI to QTEMP and set some options. Of course, since the code uses ADO, you will need to ensure that ADO is referenced in your VB project. After declaring the objects to be used in the program, the first line connects to the iSeries via an ODBC data source called MY400 with the appropriate user ID and password. Next, the program sets the ActiveConnection property of the command object CMD1 to the connection Con1. At this point, the program is ready to rock and roll.

Sample VB Code to Make a Private QAQQINI


Samples VB Code to make a Private QAQQINI

Sample Java Code to make a Private QAQQINI

The first statement that the program will execute is a call to the QSYS.QCMDEXC API. This API allows you to execute any command on the iSeries as if it is a stored procedure. The API function takes two arguments: a string that represents the command you want to run, and a decimal (15,5) number that informs the iSeries of the length of the supplied command string. In this case, I am passing the string:


This is the command that will create a new QAQQINI object in the QTEMP library. In the code examples, note that I am passing the number 75 (the length of the command string) as a decimal 15,5 in the second argument to the QCMDEXC API. Once the call to CMD.Execute is complete, there is a brand-spanking new copy of the QAQQINI object in the QTEMP library, and I am ready to begin setting some options. The next statement that my program executes is to change the value of the MESSAGE_DEBUG entry in the file to *YES. Remember, this causes all of the optimizer messages to be written to the iSeries job log. Finally, after I have finished setting the options that I want, my program again uses the QCMDEXC API to execute a call to the CHGQRYA command. This call causes my job to look for the QAQQINI file in the QTEMP library.

Now that my connection is in a happy state and my options are set the way I want them, I am free to execute a bunch of SQL statements and look at my optimizer messages to see how the optimizer is choosing to implement those statements. Or, I could continue to set other options in QAQQINI that control how the SQL in my connection will be processed.

QAQQINI is a much better way to control query processing options on your iSeries. It simplifies the management of performance settings and places most of the values that you want to access in one neat and accessible place. In addition, by using the technique of copying the QAQQINI to the QTEMP library, you can be assured that any changes you make for testing purposes will not interfere with other users. This technique will work with Java, VB or RPG, COBOL, and C on the iSeries. Play with it, learn it, and see what interesting and gross things you can make your iSeries query optimizer do!