Because the optimizer might sometimes make poor decisions as to how to best process a query, you need to know how and when to override the optimizer and force SQL Server to process a query in a specific manner. How often does SQL Server require manual intervention to execute a query optimally? Considering the overwhelming number of query types and circumstances in which those queries are run, SQL Server does a surprisingly effective job of query optimization in most instances. For all but the most grueling, complex query operations, my own testing and experience has shown that SQL Server's optimizer is quite clever ”and very, very good at wringing the best performance out of any hardware platform. For this reason, you should treat the material covered in this chapter as a collection of techniques to be used only where other methods of getting optimal query performance have already failed. Before indiscriminately applying the techniques discussed in this section, remember one very important point: Use of these features can effectively hide serious fundamental design or coding flaws in your database, application, or queries. In fact, if you're tempted to use these features (with a few more moderate exceptions), it should serve as an indicator that problems might lie elsewhere in your application. If you are satisfied that no such flaws exist and that SQL Server is choosing the wrong plan to optimize your query, you can use the methods discussed in this section to override two of the three most important decisions the optimizer makes:
The other decision made by the optimizer is the locking strategy to apply. Using table hints to override locking strategies is discussed in Chapter 38, "Locking and Performance." Throughout this section, one point must remain clear in the reader's mind: These options should be used only in exception cases to cope with specific optimization problems in specific queries in specific applications. As such, there are no standard or global rules to follow because the application of these features by definition means that normal SQL Server behavior isn't taking place. The practical result of this idea is that you should test every option in your environment, with your data and your queries, and use the techniques and methods discussed in this chapter and the other performance- related chapters to optimize and fine-tune the performance of your queries. The fastest -performing query wins, so don't be afraid to experiment with different options ”but don't think that these statements and features are globally applicable or fit general categories of problems, either! There are, in fact, only three rules: test, test, and test !
Optimizer HintsYou can specify three types of hints in a query to override the decisions made by the optimizer:
The remainder of this section will examine and describe each type of table hints. Forcing Index Selection with Table HintsIn addition to locking hints that can be specified for each table in a query, SQL Server 2000 allows you to provide table-level hints that enable you to specify the index SQL Server should use for accessing the table. The syntax for specifying an index hint is as follows : SELECT column_list FROM talename WITH (INDEX ( indid index_name [, ...]) ) The old style syntax INDEX = index_name is still supported for backward compatibility only. The new syntax, introduced in SQL Server 7.0, allows you to specify multiple indexes. You can specify the index by name or by ID. It is recommended that you specify indexes by name as the IDs for nonclustered indexes could change if they are dropped and re-created in a different order than they were originally. Specify an index ID of 0 to force a table scan. When you specify multiple indexes in the hint list, all the indexes listed are used to retrieve the rows from the table, forcing an index intersection or index covering via an index join. If the collection of indexes listed does not cover the query, a regular row fetch is performed after retrieving all the indexed columns . To get a list of indexes on a table, you can use sp_helpindex . However, the stored procedure doesn't display the index ID. To get a list of all user -defined tables and the names of the indexes defined on them, you can execute a query against the sysindexes table similar to the following, which was run in the pubs database: select 'Table name' = object_name(id), 'Index name' = name, 'Index ID' = indid from sysindexes where id > 99 /* only system tables have id less than 99 */ and indid between 1 and 254 /* do not include rows for text columns or tables without a clustered index*/ /* do not include auto statistics */ and indexproperty(id, name, 'IsAutoStatistics') = 0 order by 1, 3 Table name Index name Index ID ------------------------------ ------------------------------ -------- authors UPKCL_auidind 1 authors aunmind 2 Clustered_Dupes Cl_dupes_col1 1 clustered_nodupes idxCL 1 dtproperties pk_dtproperties 1 employee employee_ind 1 employee PK_emp_id 2 employee emp_tel_idx 3 jobs PK__jobs__117F9D94 1 nc_heap_nodupes idxNC_heap 2 pub_info UPKCL_pubinfo 1 publishers UPKCL_pubind 1 recomp_tab idx1 2 recomp_tab idx2 3 roysched titleidind 2 sales UPKCL_sales 1 sales titleidind 2 stores UPK_storeid 1 titleauthor UPKCL_taind 1 titleauthor auidind 2 titleauthor titleidind 3 titles UPKCL_titleidind 1 titles titleind 2 An index ID of 1 is for the clustered index, and index IDs 2 “254 are the nonclustered indexes. An index ID of 0 indicates a table with no clustered index, and an index ID of 255 is used if the table has any text or image columns. Remember that every table will have either a 0 or a 1 ”but not both. After you have the index names and IDs, you can use them to specify the index to be used by the query. SQL Server also supports, for backward compatibility, the FASTFIRSTROW option as a table hint. This has been replaced with the FAST n query processing hint, described in the "Specifying Query Processing Hints" section later in this chapter. Forcing Join Strategies with Join HintsJoin hints let you force the type of join that should be used between two tables. The join hints correspond with the three types of join strategies, as follows:
Join hints can be specified only when you use the ANSI-style join syntax ”that is, when you actually use the keyword JOIN in the query. The hint is specified between the type of join and the keyword JOIN , which means you can't leave out the keyword INNER for an inner join (if you are doing an outer join, the OUTER keyword always has to be specified). Thus, the syntax for the FROM clause when using join hints is as follows: FROM table1 {INNER OUTER} [LOOP MERGE HASH} JOIN table2 The following is an example of forcing SQL Server to use a hash join: select st.stor_name, ord_date, qty from stores st INNER HASH JOIN sales s on st.stor_id = s.stor_id where st.stor_id between 'B100' and 'B599' Specifying Query Processing HintsSQL Server 2000 enables you to specify additional query hints to control how your queries are optimized and processed . Query hints are specified at the very end of your query using the OPTION keyword. There can be only one OPTION clause per query, but you can specify multiple hints in an OPTION clause, as shown in the following syntax: OPTION ( hint1 [, ... hintn ]) Query hints are grouped into three categories: GROUP BY , UNION , and miscellaneous. GROUP BY HintsThe GROUP BY hints specify how GROUP BY or COMPUTE operations should be performed. The GROUP BY hints that can be specified are as follows:
Only one GROUP BY hint can be specified at a time. UNION HintsThe UNION hints specify how UNION operations should be performed. The UNION hints that can be specified are as follows:
Only one UNION hint can be specified at a time and must come after the last query in the UNION . The following is an example of forcing concatention for a UNION : select stor_id from sales where stor_id like 'B19%' UNION select title_id from titles where title_id like 'C19%' OPTION (CONCAT UNION) Miscellaneous HintsThe following miscellaneous hints can be used to override various query operations:
Limiting Query Plan Execution with the Query GovernorAnother interesting tool available in SQL Server 2000 is the query governor. Because SQL Server uses a cost-based optimizer, the cost of executing a given query is always estimated before the query is actually executed. The query governor enables you to set a cost threshold to prevent certain long-running queries from being executed. This is not so much a tuning tool as it is a performance problem prevention tool. For example, if you have an application with an English Query front end, you have no way of controlling what the user is going to request from the database and the type of query generated. The query governor will allow you to prevent a runaway query from executing and avoid using up valuable CPU time and memory by processing a poorly formed query. You can set the query governor cost limit for the current user session by setting the session level property, QUERY_GOVERNOR_COST_LIMIT : SET QUERY_GOVERNOR_COST_LIMIT value The value specified is the maximum length of time, in seconds, a query is allowed to run. If the optimizer estimates the query would take longer than the specified value, SQL Server will not execute it. Although the option is specified in seconds, it is a relative value corresponding to the TotalSubtreeCost estimated by the query optimizer. In other words, if you set the query governor cost limit to 100, it will prevent the execution of any queries whose estimated TotalSubtreeCost is greater than 100 seconds. The TotalSubtreeCost time is based on a generic algorithm in SQL Server and might not map exactly to how long the query takes to run on your own system. The actual runtime depends on a number of factors ”CPU speed, IO speed, network speed, the number of rows returned over the network, and so on. You will need to correlate the optimizer runtime estimate to how long the query actually takes to run on your system to set the query governor cost limit to a value related to actual query runtime. The best way to figure this out is to run your queries with the STATISTICS PROFILE and STATISTICS TIME session settings enabled (these settings are discussed in more detail in the next chapter, "Query Analysis"). Compare the values in the TotalSubtreeCost column for the first row of the STATISTICS PROFILE output with the elapsed time displayed by STATISTICS TIME for your query. Do this for a number of your queries and you might be able to come up with an average correlation of the actual runtimes with the optimizers' estimated query cost. For example, if the average cost estimate is 30 seconds and the actual runtimes are 15 seconds, you would need to double the setting for query governor cost limit to correspond to the actual execution time threshold ”in other words, if you want the threshold to be 60 seconds for this example, you would need to set the query governor threshold to 120. To configure a query governor threshold for all user connections, you can also set it at the server level. Open Enterprise Manager. Right-click the server and choose Properties from the menu. Next, select the Server Settings tab. In the Server Behavior group box, check the Use Query Governor option and specify a cost threshold (see Figure 35.20). The cost threshold is given in the same TotalSubtreeCost units as specified for the QUERY_GOVERNOR_COST_LIMIT session setting. Figure 35.20. Configuring the Query Governor settings in the SQL Server Properties dialog box.
Alternatively, you can configure the serverwide setting using sp_configure: sp_configure query governor cost limit, 100 |