Microsoft SQL Server includes a very intelligent cost-based query optimizer which, given an ad-hoc query, can quickly determine the best access method for retrieving the data, including the order in which to join tables and whether or not to use indexes that may be on those tables. By using a cost-based query optimizer, the System Administrator or end user is released from having to determine the most efficient way of structuring the query to get optimal performance -- instead, the optimizer looks at all possible join orders, and the cost of using each index, and picks the plan with the least cost in terms of page I/O's.
Detailed information on the final access method that the optimizer chooses can be displayed for the user by executing the Transact-SQL "SET SHOWPLAN ON" command. This command will show each step that the optimizer uses in joining tables and which, if any, indexes it chooses to be the least-cost method of accessing the data. This can be extremely beneficial when analyzing certain queries to determine if the indexes that have been defined on a table are actually being considered by the optimizer as useful in getting to the data. This document will define and explain each of the output messages from SHOWPLAN, and give example queries and the output from SHOWPLAN to illustrate the point. The format will be consistent throughout: a heading which corresponds to the exact text of a SHOWPLAN statement, followed by a description of what it means, a sample query which generates that particular message, and the full output from executing the query with the SHOWPLAN option on. Wherever possible, the queries will use the existing tables and indexes, unaltered, from the SQL Server "Pubs" sample database.
Query: SELECT au_lname, au_fname
FROM Authors
WHERE city = "Oakland"
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
authors
Nested iteration
Table Scan
Query: SELECT city, total_authors = count(*)
FROM Authors
GROUP BY city
SHOWPLAN: STEP 1
The type of query is SELECT (into a
worktable)
GROUP BY
Vector Aggregate
FROM TABLE
authors
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
FROM TABLE
Worktable
Nested iteration
Table Scan
This statement describes the type of query for each step. For most user queries, the value for <query type> will be SELECT, INSERT, UPDATE, or DELETE. If SHOWPLAN is turned on while other commands are issued, the <query type> will reflect the command that was issued. The following examples show various outputs for different queries/commands:
Query 1: CREATE TABLE Mytab (col1 int)
SHOWPLAN 1: STEP 1
The type of query is TABCREATE
Query 2: INSERT Publishers
VALUES ("9904", "NewPubs", "Seattle", "WA")
SHOWPLAN 2: STEP 1
The type of query is INSERT
The update mode is direct
Table Scan
TO TABLE
publishers
Query 1: UPDATE Mytable
SET col1 = col1 + 1
SHOWPLAN 1: STEP 1
The type of query is UPDATE
The update mode is deferred
FROM TABLE
Mytable
Nested iteration
Table Scan
TO TABLE
Mytable
Consider the consequences of starting at the first row in the
table, and updating each row, through the end of the table. Updating the
first row (which has an initial value of 1) to 2 would cause an error, as
the unique index would be violated since there is already a value of 2 in
the table; likewise, updating the second row (which has an initial value of
2) to 3 would also cause a unique key violation, as would all rows
through the end of the table, except for the last row. By using deferred
updates, this problem is easily avoided. The log records are first
constructed to show what the new values for each row will be, the
existing rows are deleted, and the new values inserted.
Just as with UPDATE commands, INSERT commands may also be deferred for very similar reasons. Consider the following query (there is no clustered index or unique index on the "roysched" table):
Query 2: INSERT roysched SELECT * FROM roysched
SHOWPLAN 2: STEP 1
The type of query is INSERT
The update mode is deferred
FROM TABLE
roysched
Nested iteration
Table Scan
TO TABLE
roysched
Since there is no clustered index on the table, the new rows will
be added to the end of the table. The query processor needs to be able to
differentiate between the existing rows that are currently in the table
(prior to the INSERT command) and the rows which will be inserted, so
as to not get into a continuous loop of selecting a row, inserting it at the
end of the table, selecting that row that it just inserted, and re-inserting it
again. By using the deferred method of inserting, the log records can be
first be constructed to show all of the currently-existing values in the
table, then SQL Server will re-read those log records to insert them into
the table.
Query 1: DELETE
FROM authors
WHERE au_id = "172-32-1176"
SHOWPLAN 1: STEP 1
The type of query is DELETE
The update mode is direct
FROM TABLE
authors
Nested iteration
Using Clustered Index
TO TABLE
authors
Query 2: UPDATE titles
SET type = "popular_comp"
WHERE title_id = "BU2075"
SHOWPLAN 2: STEP 1
The type of query is UPDATE
The update mode is direct
FROM TABLE
titles
Nested iteration
Using Clustered Index
TO TABLE
titles
Query 3: UPDATE titles
SET price = $5.99
WHERE title_id = "BU2075"
SHOWPLAN 3: STEP 1
The type of query is UPDATE
The update mode is deferred
FROM TABLE
titles
Nested iteration
Using Clustered Index
TO TABLE
titles
Note that the only difference between the second and third
example queries is the column of the table which is being updated. In the
second query, the direct update method is used, whereas in the third
query, the deferred method is used. This difference is due to the datatype
of the column being updated: the titles.type column is defined as
"char(12) NOT NULL", while the titles.price column is defined as
"money NULL". Since the titles.price column is not a fixed-length
datatype, the direct method cannot be used.
Query: SELECT type, AVG(advance),
SUM(ytd_sales)
FROM titles
GROUP BY type
SHOWPLAN: STEP 1
The type of query is SELECT (into a
worktable)
GROUP BY
Vector Aggregate
FROM TABLE
titles
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
FROM TABLE
Worktable
Nested iteration
Table Scan
Query: SELECT AVG(advance), SUM(ytd_sales)
FROM titles
WHERE type = "business"
SHOWPLAN: STEP 1
The type of query is SELECT
Scalar Aggregate
FROM TABLE
titles
Nested iteration
Table Scan
STEP 2
The type of query is SELECT
Table Scan
Notice that SHOWPLAN considers this a two-step query, which
is very similar to the SHOWPLAN from the GROUP BY query listed
earlier. Since the query contains a scalar aggregate, which will return a
single value, SQL Server keeps internally a "variable" to store the result
of the aggregate function. It can be thought of as a temporary storage
space to keep a running total of the aggregate function as the qualifying
rows from the table are evaluated. After all rows have been evaluated
from the table (Step 1), the final value from the "variable" is then selected
(Step 2) to return the scalar aggregate result.
Query: SELECT title_id, AVG(qty)
FROM sales
GROUP BY title_id
SHOWPLAN: STEP 1
The type of query is SELECT (into a
worktable)
GROUP BY
Vector Aggregate
FROM TABLE
sales
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
FROM TABLE
Worktable
Nested iteration
Table Scan
Query: SELECT authors.au_id, au_fname, au_lname
FROM authors, titleauthor, titles
WHERE authors.au_id = titleauthor.au_id
AND titleauthor.title_id = titles.title_id
AND titles.type = "psychology"
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
titles
Nested iteration
Table Scan
FROM TABLE
titleauthor
Nested iteration
Table Scan
FROM TABLE
authors
Nested iteration
Table Scan
This query illustrates the order in which the SQL Server query
optimizer chooses to join the tables, which is not the order that
they were listed in the FROM clause or the WHERE clause. By
examining the order of the "FROM TABLE" statements, it can be seen
that the qualifying rows from the titles table are first located
(using the search clause <titles.type = "psychology">). Those
rows are then joined with the titleauthor table (using the join
clause <titleauthor.title_id = titles.title_id>), and finally
the titleauthor table is joined with the authors table to retrieve
the desired columns (using the join clause <authors.au_id =
titleauthor.au_id>).
Query 1: INSERT sales
VALUES ("8042", "QA973", "7/15/92", 7,
"Net 30", "PC1035")
SHOWPLAN 1: STEP 1
The type of query is INSERT
The update mode is direct
Table Scan
TO TABLE
sales
Query 2: UPDATE publishers
SET city = "Los Angeles"
WHERE pub_id = "1389"
SHOWPLAN 2: STEP 1
The type of query is UPDATE
The update mode is deferred
FROM TABLE
publishers
Nested iteration
Using Clustered Index
TO TABLE
publishers
Notice that the SHOWPLAN for the second query indicates that
the publishers table is used both as the "FROM TABLE" as well as the
"TO TABLE". In the case of UPDATE operations, the optimizer needs to
read the table which contains the row(s) to be updated, resulting in the
"FROM TABLE" statement, and then needs to modify the row(s),
resulting in the "TO TABLE" statement.
Worktables will always need to be used when a query contains a GROUP BY clause. For queries involving ORDER BY, it is possible that the ordering can be done without the use of the worktable. If there is a clustered index on the column(s) in the ORDER BY clause, the optimizer knows that the rows are already stored in sorted order, so a sort in a worktable is not necessary (although there are exceptions to this, depending on the sort order which is installed on the server). Since the data is not stored in sorted order for nonclustered indexes, the worktable will not be necessary if the cheapest access plan is by using the nonclustered index. However, if the optimizer determines that scanning the entire table will require fewer I/Os than using the nonclustered index, then a worktable will need to be created for the ordering of the results. The following examples illustrate the use of worktables:
Query 1: SELECT type, AVG(advance), SUM(ytd_sales)
FROM titles
GROUP BY type
SHOWPLAN 1: STEP 1
The type of query is SELECT (into a
worktable)
GROUP BY
Vector Aggregate
FROM TABLE
titles
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
FROM TABLE
Worktable
Nested iteration
Table Scan
Query 2: SELECT *
FROM authors
ORDER BY au_lname, au_fname
SHOWPLAN 2: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for ORDER BY
FROM TABLE
authors
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
This step involves sorting
FROM TABLE
Worktable
Using GETSORTED
Table Scan
Query 3: SELECT *
FROM authors
ORDER BY au_id
SHOWPLAN 3: STEP 1
The type of query is SELECT
FROM TABLE
authors
Nested iteration
Table Scan
In the third example above, notice that no worktable was created
for the ORDER BY clause. This is because there is a unique clustered
index on the authors.au_id column, so the data is already stored in sorted
order based on the au_id value, and an additional sort for the ORDER
BY is not necessary. In the second example, there is a composite
nonclustered index on the columns au_lname and au_fname. However,
since the optimizer chose not to use the index, and due to the sort order
on the SQL Server, a worktable needed to be created to accomodate the
sort.
Query: SELECT *
INTO seattle_stores
FROM stores
WHERE city = "seattle"
SHOWPLAN: STEP 1
The type of query is TABCREATE
STEP 2
The type of query is INSERT
The update mode is direct
Worktable created for SELECT_INTO
FROM TABLE
stores
Nested iteration
Table Scan
TO TABLE
Worktable
Query: SELECT DISTINCT city
FROM authors
SHOWPLAN: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for DISTINCT
FROM TABLE
authors
FROM TABLE
authors
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
This step involves sorting
FROM TABLE
Worktable
Using GETSORTED
Table Scan
Query: SELECT *
FROM authors
ORDER BY city
SHOWPLAN: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for ORDER BY
FROM TABLE
authors
FROM TABLE
authors
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
This step involves sorting
FROM TABLE
Worktable
Using GETSORTED
Table Scan
Query: SELECT Tab1.col1
FROM Tab1, Tab2
WHERE Tab1.col1 = Tab2.col1
SHOWPLAN: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for REFORMATTING
FROM TABLE
Tab2
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
FROM TABLE
Tab1
Nested iteration
Table Scan
FROM TABLE
Worktable
Nested iteration
Using Clustered Index
Query: SELECT DISTINCT state
FROM stores
SHOWPLAN: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for DISTINCT
FROM TABLE
stores
FROM TABLE
stores
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
This step involves sorting
FROM TABLE
Worktable
Using GETSORTED
Table Scan
Query: SELECT au_id, au_lname, au_fname, city
FROM authors
ORDER BY city
SHOWPLAN: STEP 1
The type of query is INSERT
The update mode is direct
Worktable created for ORDER BY
FROM TABLE
authors
FROM TABLE
authors
Nested iteration
Table Scan
TO TABLE
Worktable
STEP 2
The type of query is SELECT
This step involves sorting
FROM TABLE
Worktable
Using GETSORTED
Table Scan
Query: SELECT title_id, title
FROM titles, publishers
WHERE titles.pub_id = publishers.pub_id
AND publishers.pub_id = '1389'
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
publishers
Nested iteration
Using Clustered Index
FROM TABLE
titles
Nested iteration
Table Scan
Query 1: SELECT au_lname, au_fname
FROM authors
WHERE EXISTS
(SELECT *
FROM publishers
WHERE authors.city = publishers.city)
SHOWPLAN 1: STEP 1
The type of query is SELECT
FROM TABLE
authors
Nested iteration
Table Scan
FROM TABLE
publishers
EXISTS TABLE : nested iteration
Table Scan
Query 2: SELECT title
FROM titles
WHERE pub_id IN
(SELECT pub_id
FROM publishers
WHERE city LIKE "B%")
SHOWPLAN 2: STEP 1
The type of query is SELECT
FROM TABLE
titles
Nested iteration
Table Scan
FROM TABLE
publishers
EXISTS TABLE : nested iteration
Table Scan
Query: SELECT au_lname, au_fname
FROM authors
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
authors
Nested iteration
Table Scan
Query: SELECT title_id, title
FROM titles
WHERE title_id LIKE "PS2%"
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
titles
Nested iteration
Using Clustered Index
Query: SELECT *
FROM master..sysobjects
WHERE name = "mytable"
AND uid = 5
SHOWPLAN: STEP 1
The type of query is SELECT
FROM TABLE
master..sysobjects
Nested iteration
Index : ncsysobjects
The OR strategy is not limited only to queries that contain OR clauses. When an IN clause is used to list a group of possible values, SQL Server interprets that the same way as though the query had a separate equality clause for each of the values in the IN clause. To illustrate the OR strategy and the use of the Dynamic Index, the queries will be based on a table with 10,000 unique data rows, a unique nonclustered index on column "col1", and a unique nonclustered index on column "col2".
Query 1: SELECT *
FROM Mytable
WHERE col1 = 355
OR col2 = 732
SHOWPLAN 1: STEP 1
The type of query is SELECT
FROM TABLE
Mytable
Nested iteration
Index : col1_idx
FROM TABLE
Mytable
Nested iteration
Index : col2_idx
FROM TABLE
Mytable
Nested iteration
Using Dynamic Index
Query 2: SELECT *
FROM Mytable
WHERE col1 IN (700, 1503, 311)
SHOWPLAN 2: STEP 1
The type of query is SELECT
FROM TABLE
Mytable
Nested iteration
Index : col1_idx
FROM TABLE
Mytable
Nested iteration
Index : col1_idx
FROM TABLE
Mytable
Nested iteration
Index : col1_idx
FROM TABLE
Mytable
Nested iteration
Using Dynamic Index
SQL Server does not always resort to using the OR strategy for
every query that contains OR clauses. The following conditions must be
met before it will choose to use the OR strategy: