Microsoft - Query Processing Enhancements On Partitioned Tables and Indexes (Partition Elimination)
Microsoft - Query Processing Enhancements On Partitioned Tables and Indexes (Partition Elimination)
105)
Note
Partitioned tables and indexes are supported only in the SQL Server Enterprise, Developer, and
Evaluation editions.
In addition, the query optimizer is extended so that a seek or scan operation with one condition can be
done on PartitionID (as the logical leading column) and possibly other index key columns, and then a
second-level seek, with a different condition, can be done on one or more additional columns, for each
distinct value that meets the qualification for the first-level seek operation. That is, this operation, called
a skip scan, allows the query optimizer to perform a seek or scan operation based on one condition to
determine the partitions to be accessed and a second-level index seek operation within that operator
to return rows from these partitions that meet a different condition. For example, consider the following
query.
CREATE PARTITION FUNCTION myRangePF1 (int) AS RANGE LEFT FOR VALUES (3, 7, 10);
To solve the query, the query processor performs a first-level seek operation to find every partition that
contains rows that meet the condition T.a < 10. This identifies the partitions to be accessed. Within
each partition identified, the processor then performs a second-level seek into the clustered index on
column b to find the rows that meet the condition T.b = 2 and T.a < 10.
The following illustration is a logical representation of the skip scan operation. It shows table T with
data in columns a and b. The partitions are numbered 1 through 4 with the partition boundaries shown
by dashed vertical lines. A first-level seek operation to the partitions (not shown in the illustration) has
determined that partitions 1, 2, and 3 meet the seek condition implied by the partitioning defined for
the table and the predicate on column a. That is, T.a < 10. The path traversed by the second-level seek
portion of the skip scan operation is illustrated by the curved line. Essentially, the skip scan operation
seeks into each of these partitions for rows that meet the condition b = 2. The total cost of the skip
scan operation is the same as that of three separate index seeks.
The operations such as scans, seeks, inserts, updates, merges, and deletes that access partitioned
tables or indexes.
The partitions accessed by the query. For example, the total count of partitions accessed and the
ranges of contiguous partitions that are accessed are available in run-time execution plans.
When the skip scan operation is used in a seek or scan operation to retrieve data from one or
more partitions.
For more information about displaying execution plans, see Execution Plan How-to Topics.
SQL Server 2008 provides enhanced partitioning information for both compile-time and run-time
execution plans. Execution plans now provide the following information:
An optional Partitioned attribute that indicates that an operator, such as a seek, scan, insert,
update, merge, or delete, is performed on a partitioned table.
A new SeekPredicateNew element with a SeekKeys subelement that includes PartitionID as the
leading index key column and filter conditions that specify range seeks on PartitionID. The
presence of two SeekKeys subelements indicates that a skip scan operation on PartitionID is used.
Summary information that provides a total count of the partitions accessed. This information is
available only in run-time plans.
To demonstrate how this information is displayed in both the graphical execution plan output and the
XML Showplan output, consider the following query on the partitioned table fact_sales. This query
updates data in two partitions.
UPDATE fact_sales
SET quantity = quantity * 2
WHERE date_id BETWEEN 20080802 AND 20080902;
The following illustration shows the properties of the Clustered Index Seek operator in the compile-
time execution plan for this query.
To view the definition of the fact_sales table and the partition definition, see "Example" in this topic.
Partitioned Attribute
The Partitioned attribute can appear in the following physical and logical operators:
Table Scan
Index Scan
Index Seek
Insert
Update
Delete
Merge
As shown in the previous illustration, this attribute is displayed in the properties of the operator in
which it is defined. In the XML Showplan output, this attribute appears as Partitioned="1" in
the RelOp node of the operator in which it is defined.
In XML Showplan output, the SeekPredicateNew element appears in the operator in which it is
defined. It can contain up to two occurrences of the SeekKeys sub-element. The first SeekKeys item
specifies the first-level seek operation at the partition ID level of the logical index. That is, this seek
determines the partitions that must be accessed to satisfy the conditions of the query. The
second SeekKeys item specifies the second-level seek portion of the skip scan operation that occurs
within each partition identified in the first-level seek.
In run-time execution plans, partition summary information provides a count of the partitions accessed
and the identity of the actual partitions accessed. You can use this information to verify that the correct
partitions are accessed in the query and that all other partitions are eliminated from consideration.
The following information is provided: Actual Partition Count, and Partitions Accessed.
Actual Partition Count is the total number of partitions accessed by the query.
Partitions Accessed, in XML Showplan output, is the partition summary information that appears in
the new RuntimePartitionSummary element in RelOp node of the operator in which it is defined.
The following example shows the contents of the RuntimePartitionSummary element, indicating that
two total partitions are accessed (partitions 2 and 3).
<RunTimePartitionSummary>
</PartitionsAccessed>
</RunTimePartitionSummary>
The Showplan methods SHOWPLAN_ALL, SHOWPLAN_TEXT and STATISTICS PROFILE do not report the
partition information described in this topic, with the following exception. As part of
the SEEK predicate, the partitions to be accessed are identified by a range predicate on the computed
column representing the partition ID. The following example shows the SEEK predicate for a Clustered
Index Seek operator. Partitions 2 and 3 are accessed, and the seek operator filters on the rows that
meet the condition date_id BETWEEN 20080802 AND 20080902.
ORDERED FORWARD)
In a collocated plan, the Nested Loops join reads one or more joined table or index partitions from the
inner side. The numbers within the Constant Scan operators represent the partition numbers.
When parallel plans for collocated joins are generated for partitioned tables or indexes,
a Parallelism operator appears between the Constant Scan and the Nested Loops join operators. In
this case, multiple threads on the outer side of the join each read and work on a different partition.
The following illustration demonstrates a parallel query plan for a collocated join.
If the number of threads is equal to the number of partitions, the query processor assigns one thread
to each partition. When a thread finishes, it is not reallocated to another partition.
If the number of threads is greater than the number of partitions, the query processor allocates an
equal number of threads to each partition. If the number of threads is not an exact multiple of the
number of partitions, the query processor allocates one additional thread to some partitions in order
to use all of the available threads. Note that if there is only one partition, all threads will be assigned to
that partition. In the diagram below, there are four partitions and 14 threads. Each partition has 3
threads assigned, and two partitions have an additional thread, for a total of 14 thread assignments.
When a thread finishes, it is not reassigned to another partition.
Although the above examples suggest a straightforward way to allocate threads, the actual strategy is
more complex and accounts for other variables that occur during query execution. For example, if the
table is partitioned and has a clustered index on column A and a query has the predicate clause WHERE
A IN (13, 17, 25), the query processor will allocate one or more threads to each of these three seek
values (A=13, A=17, and A=25) instead of each table partition. It is only necessary to execute the query
in the partitions that contain these values, and if all of these seek predicates happen to be in the same
table partition, all of the threads will be assigned to the same table partition.
To take another example, suppose that the table has four partitions on column A with boundary points
(10, 20, 30), an index on column B, and the query has a predicate clause WHERE B IN (50, 100, 150).
Because the table partitions are based on the values of A, the values of B can occur in any of the table
partitions. Thus, the query processor will seek for each of the three values of B (50, 100, 150) in each of
the four table partitions. The query processor will assign threads proportionately so that it can execute
each of these 12 query scans in parallel.
Table partitions based on column A Seeks for column B in each table partition
Table Partition 1: A < 10 B=50, B=100, B=150
Table Partition 2: A >= 10 AND A < 20 B=50, B=100, B=150
Table Partition 3: A >= 20 AND A < 30 B=50, B=100, B=150
Table Partition 4: A >= 30 B=50, B=100, B=150
Best Practices
To improve the performance of queries that access a large amount of data from large partitioned tables
and indexes, we recommend the following best practices:
Example
The following example creates a test database containing a single table with seven partitions. Use the
tools described previously when executing the queries in this example to view partitioning information
for both compile-time and run-time plans.
Note
This example inserts more than 1 million rows into the table. Running this example may take several
minutes depending on your hardware. Before executing this example, verify that you have more than
1.5 GB of disk space available.
USE master;
GO
USE db_sales_test;
GO
DECLARE @i int;
SET @i = 1;
WHILE (@i<1000000)
BEGIN
INSERT INTO fact_sales
VALUES(20080800 + (@i%30) + 1, @i%10000, @i%200, RAND() * 25, (@i%3) + 1, '');
SET @i += 1;
END;
GO
DECLARE @i int;
SET @i = 1;
WHILE (@i<10000)
BEGIN
INSERT INTO fact_sales
VALUES(20080900 + (@i%30) + 1, @i%10000, @i%200, RAND() * 25, (@i%3) + 1, '');
SET @i += 1;
END;
PRINT 'Done.';
GO
-- Two-partition query.
SET STATISTICS XML ON;
GO