Indexing for Partitioned Tables
From SQLServerPedia
|
Once you have partitioned your table, you’re ready to tune for performance. As with any table, indexing is a great place to start. If you’re new to partitioning, you probably created all of your indexes on the partitioned scheme, either by design or unintentionally.
What is a Partitioned Index?A partitioned index, like a partitioned table, separates data into different physical structures (partitions) under one logical name. Specifically, each partition in a non-clustered index contains its own B-tree structure with a subset of rows, based upon the partitioning scheme. By default, an unpartitioned, non-clustered index has just one partition.
CREATE NONCLUSTERED INDEX IX_myIndex
ON dbo.myTable(myColumn);
CREATE NONCLUSTERED INDEX IX_myIndex
ON dbo.myTable(myColumn)
ON [PRIMARY];
When Should You Use a Partitioned Index?But should you partition your index? That depends on how you use it. In fact, most environments are best served by a mix of partitioned and non-partitioned indexes. Two tips:
Examples: Let’s set up some indexes and see how different queries perform.
/* Create a partition function. */
CREATE Partition FUNCTION
[test_monthlyDateRange_pf] (DATETIME)
AS Range RIGHT FOR VALUES
('2009-01-01', '2009-01-08', '2009-01-15'
, '2009-01-22', '2009-01-29');
Go
/* Associate the partition function with a partition scheme. */
CREATE Partition Scheme test_monthlyDateRange_ps
AS Partition test_monthlyDateRange_pf
All TO ([PRIMARY]);
Go
/* Create a partitioned table. */
CREATE TABLE dbo.orders
(
order_id INT IDENTITY(1,1) Not Null
, orderDate DATETIME Not Null
, orderData SMALLDATETIME Not Null
CONSTRAINT PK_orders PRIMARY KEY CLUSTERED
(
order_id
, orderDate
)
) ON test_monthlyDateRange_ps(orderDate);
Go
/* Create some records to play with. */
SET NOCOUNT ON;
DECLARE @endDate DATETIME = '2009-01-01';
WHILE @endDate < '2009-02-01'
BEGIN
INSERT INTO dbo.orders
SELECT @endDate, @endDate;
SET @endDate = DATEADD(MINUTE, 1, @endDate);
END;
SET NOCOUNT OFF;
/* Let’s create an aligned, partitioned index. */
CREATE NONCLUSTERED INDEX IX_orders_aligned
ON dbo.orders(order_id)
ON test_monthlyDateRange_ps(orderDate);
/* you don't actually need to declare the last
line of this unless you want to create the
index on a different partitioning scheme. */
/* Now let’s create an unpartitioned index. */
CREATE NONCLUSTERED INDEX IX_orders_unpartitioned
ON dbo.orders(order_id)
ON [PRIMARY];
Now that we have both a partitioned index and an unpartitioned index, let’s take a look at our sys.partitions table:
/* Let's take a look at our index partitions */
SELECT i.name
, i.index_id
, p.partition_number
, p.ROWS
FROM sys.partitions AS p
Join sys.indexes AS i
ON p.OBJECT_ID = i.OBJECT_ID
And p.index_id = i.index_id
WHERE p.OBJECT_ID = OBJECT_ID('orders')
ORDER BY i.index_id, p.partition_number;
sys.partitions
Now that we have our environments set up, let’s run through some different queries and see the performance impact of each type of index.
Query 1: Look Up a Specific Record Using a Seek/* Query 1, specific record lookup, covered */ SELECT order_id, orderDate FROM dbo.orders WITH (INDEX(IX_orders_aligned)) WHERE order_id = 25000; SELECT order_id, orderDate FROM dbo.orders WITH (INDEX(IX_orders_unpartitioned)) WHERE order_id = 25000; Query 1
Query 2: Look Up a Specific Record Using a ScanNow let’s try the same query, but utilizing a scan instead of a seek: /* Query 2, specific record look-up, uncovered */ SELECT order_id, orderDate, orderData FROM dbo.orders WITH (INDEX(IX_orders_aligned)) WHERE order_id = 30000; SELECT order_id, orderDate, orderData FROM dbo.orders WITH (INDEX(IX_orders_unpartitioned)) WHERE order_id = 30000; Query 2
Query 3: AggregationSo when would we want to use a partitioned index? Two instances immediately pop to mind. First, partition switching can only be performed when all indexes on a table are aligned. Second, partitioned indexes perform better when large data sets are being manipulated.
/* Query 3, aggregation */
SELECT CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME) AS 'order_date'
, COUNT(*)
FROM dbo.orders WITH (INDEX(IX_orders_aligned))
WHERE orderDate Between '2009-01-01' And '2009-01-07 23:59'
GROUP BY CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME)
ORDER BY CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME);
SELECT CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME) AS 'order_date'
, COUNT(*)
FROM dbo.orders WITH (INDEX(IX_orders_unpartitioned))
WHERE orderDate Between '2009-01-01' And '2009-01-07 23:59'
GROUP BY CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME)
ORDER BY CAST(ROUND(CAST(orderdate AS FLOAT), 0, 1) AS SMALLDATETIME);
Query 3
SummaryNow you have a general idea of what a partitioned index is and when to use a partitioned index and when to use a non-partitioned index. Ultimately, your indexing needs will depend largely on the application and how the data is used.
Author CreditsThis wiki article was adapted from a blog post by Michelle Ufford. Michelle is a SQL Developer DBA for GoDaddy.com, where she works with high-volume, mission-critical databases. She has over a decade of experience in a variety of technical roles and has worked with SQL Server for the last 5 years. She enjoys performance tuning and maintains an active SQL Server blog. Her online presences include:
Related ReadingFor more information about partitioning, check out these articles:
More SQLServerPedia Wiki Articles on SQL Server PartitioningWhen To Use SQL Server Partitioning
How to Implement Partitioned Tables |



