Partitioning Example
The following code will walk you through the process of creating a partitioned table:
-------------------------------- -- Create A Partitioned Table -- -------------------------------- /* Create a partition function. */ Create Partition Function [test_monthlyDateRange_pf] (smalldatetime) As Range Right For Values ('2008-01-01', '2008-02-01', '2008-03-01'); 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 your first partitioned table! Make sure the data types match. */ Create Table dbo.orders ( order_id int Identity(1,1) Not Null , orderDate smalldatetime Not Null Constraint PK_orders Primary Key Clustered ( orderDate , order_id ) ) On test_monthlyDateRange_ps(orderDate); Go /* Create some records to play with. */ Insert Into dbo.orders Select '2007-12-31' Union All Select '2008-01-02' Union All Select '2008-01-03' Union All Select '2008-01-04' Union All Select '2008-02-01' Union All Select '2008-02-02' Union All Select '2008-03-01' Union All Select '2008-03-02'; /* The $partition function can be used to interrogate partition data. Let's use it to see where those records are physically located. */ Select $partition.test_monthlyDateRange_pf(orderDate) As 'partition_number' , * From dbo.orders; /* By default, all new indexes are created on the partition. Let's create an aligned index */ Create NonClustered Index IX_orders_aligned On dbo.orders(order_id) On test_monthlyDateRange_ps(orderDate); /* Now let's create an un-aligned index. We'll need to specify the filegroup. */ Create NonClustered Index IX_orders_nonaligned On dbo.orders(order_id) On [Primary]; -- can be any filegroup /* Review your indexes */ Execute sp_helpindex orders;
Using the previous code as a building block, let's try swapping partitions:
-------------------------- -- Swap Out A Partition -- -------------------------- /* We need to drop our un-aligned index; otherwise we'll get an error when we attempt to do the switch. */ Drop Index IX_orders_nonaligned On dbo.orders; /* Create the table to hold the data you're swapping out. The table structures must match identically; however, DO NOT partition this table. */ Create Table dbo.orders_stage_swapOut ( order_id int Not Null , orderDate smalldatetime Not Null Constraint PK_orders_stage_swapOut Primary Key Clustered ( orderDate , order_id ) ) On [Primary]; Go /* Create the table to hold the data you're swapping in. The table structures must match identically; however, DO NOT partition this table. */ Create Table dbo.orders_stage_swapIn ( order_id int Not Null , orderDate smalldatetime Not Null Constraint PK_orders_stage_swapIn Primary Key Clustered ( orderDate , order_id ) ) On [Primary]; Go /* Populate the table you're swapping in. */ Insert Into dbo.orders_stage_swapIn Select -5, '2008-02-02' Union All Select -4, '2008-02-03' Union All Select -3, '2008-02-04' Union All Select -2, '2008-02-05' Union All Select -1, '2008-02-06'; /* Create any indexes on your table to match the indexes on your partitioned table. */ Create NonClustered Index IX_orders_stage_swapIn On dbo.orders_stage_swapIn(order_id); /* Add a check constraint for the partition to be swapped in. This step is required. */ Alter Table dbo.orders_stage_swapIn With Check Add Constraint orders_stage_swapIn_orderDateCK Check (orderDate >= '2008-02-01' And orderDate < '2008-03-01'); Go /* Swap out the old partition. */ Alter Table dbo.orders Switch Partition 3 To dbo.orders_stage_swapOut; Go /* Swap in the new partition. */ Alter Table dbo.orders_stage_swapIn Switch To dbo.orders Partition 3; Go --------------------- -- Check your data -- --------------------- /* You should have 2 records in here. */ Select * From dbo.orders_stage_swapOut; /* You should have 5 records here. */ Select * From dbo.orders Where orderDate >= '2008-02-01' And orderDate < '2008-03-01'; /* There should be no records in this table. */ Select * From dbo.orders_stage_swapIn; Select $partition.test_monthlyDateRange_pf(orderDate) As 'partition_number' , * From dbo.orders; /* Clean-up time! Drop Table dbo.orders Drop Table dbo.orders_stage_swapOut Drop Table dbo.orders_stage_swapIn Drop Partition Scheme test_monthlyDateRange_ps Drop Partition Function [test_monthlyDateRange_pf] */
Pretty easy, huh?
Partitioning 101
A few posts ago, I briefly discussed partitioning. This article will continue that discussion and focus on horizontal partitioning within SQL Server 2005.
What is partitioning?
As I've mentioned before, horizontal partitioning is the physical segregation of a single logical table name into multiple, *identical* physical structures. In SQL 2005, all table and index pages actually exist on a partition. However, if you've never created a partitioning scheme, then by default each of your tables and indexes contain just a single partition.
I'll also be talking about aligned indexes and tables, so let's go ahead and define that here. Two tables sharing the same partitioning function and the same partitioning key are aligned. Similarly, a partitioned index on a partitioned table sharing the same partitioning key is called an aligned index. Having tables and indexes in alignment can reduce CPU and improve parallel plan execution.
Why partitioning?
The two most common reasons to implement partitioning are performance improvement and ease of archiving. Let's talk about performance improvement first. We made the decision to implement partitioning in one of our data warehouses because of the size of the tables. We had a couple of tables that exceeded a billion rows, and several tables that were fast approaching that line. Upon deploying partitioning, CPU utilization dropped by ~25%, and our regular CPU and disk spikes caused by reporting processes were all but eliminated. Many of our stored procedures experienced reduced durations and reads in the range of 30-60%, and some had even greater improvements.
Why were we seeing this improvement? Many of the stored procedures were accessing data for recent dates. Knowing this helped us to decide on a weekly partitioning scheme. Stored procedures joining on aligned tables were now only accessing a small portion of the data, resulting in the aforementioned improvements. In addition, ad hocs that performed index scans now completed in a fraction of the time because the amount of data being scanned was significantly reduced.
Table maintenance also saw drastic improvement. It was previously unheard of to attempt an online index defrag on some of the largest tables without first scheduling down-time. Now, these same tables were being defragged nightly in one minute or less, without issue. This is because index defrag operations can be performed by partition. This is a great resource save; obviously, once a partition is no longer written to and is fully defragmented, it will not become re-fragmented, so there is no need to waste resources on it. Refer to my Index Defrag Script for an example on how to automate this process.
Another great feature of partitioned tables is the ability to swap partitions in and out. I took advantage of this feature with great success when backfilling data. My backfill strategy primarily consisted of bcp'ing data in weekly chunks to a staging database. The table I was backfilling had a text column where one record happened to contain the column deliminator for the bcp file. This of course wasn't caught until auditing data counts prior to deployment, when it was noticed that one week was short about ~2mm records. Now, this was ~2mm records out of ~800mm total records. The solution? Re-run the bcp process, using a different deliminator, dump the data in a staging table, and swap out the partition. The actual swap out took 2 seconds and there was no impact to the server. It was beautiful.
Swapping out partitions is particularly beneficial for archiving. Instead of writing a batch process to loop through and delete expired data, which is both time consuming and resource intensive, a partition can be swapped out and dropped in a matter of seconds. Keep in mind that, in SQL Server 2005, partitions can only be swapped out on non-replicated tables. SQL 2008 introduces functionality to allow partition swapping on a replicated table. Check out Replicating Partitioned Tables and Indexes.
That sounds great! Where do I sign up?
There are a few things to consider prior to implementing partitioning in your environment. One is the performance hit to writes. I've noticed around a 10% decrease in write performance in my environment. This is partly due to the overhead required to look up which partition the record should be written to. For this reason, I only partition tables that I expect to grow quickly (my rule of thumb is >10mm records per week), or tables that have regular archiving needs.
There's also a performance hit when querying over many partitions. Basically, think of a giant UNION ALL statement that occurs every time more than one partition is accessed. With our production hardware, we noticed that duration was still less until you hit around 30 partitions. After that, the duration was greater for partitioned tables versus non-partitioned, but reads were still less overall. This will probably vary depending on your environment, so run some tests and make sure you know what kind of impact to expect. This will also help you to determine your partitioning scheme. For example, you wouldn't want to partition on a date column using a weekly scheme if every report needs to access data for the previous 12 months.
Also make sure you have a good candidate for partitioning. That is, do not partition on a column that is frequently changed or is written non-sequentially. Both can cause overhead to your environment and may negate the benefits of partitioning. Keep in mind that when the value of the partitioning key is changed, the record is not relocated to the correct partition. Some ideal keys include surrogate identity and sequential datetime columns.
Other things to know...
• The partitioning function requires the declaration of a parameter data type. To partition a table, the partitioning key is defined during new table creation. The data types of the partitioning keys must match. For example, if you create a partitioning function using a datetime data type and try to apply it to a table with a smalldatetime column as the partitioning key, the CREATE TABLE statement will fail.
• Ensure that the partitioning key is *always* used in a query on a partitioned table. If the partitioning key is not included in the query search criteria, then the partitioned table will perform worse than a non-partitioned table.
• By default, any index created on a partitioned table is also partitioned. This is useful for creating aligned indexes, which are typically desired. Sometimes, however, you may want to create a non-aligned or non-partitioned index. In these cases, make sure to explicitly declare the desired filegroup during the index creation process.
• Temporary tables can also be partitioned; however, this would probably not make sense in most circumstances.
• Partitioning functions need to be monitored to ensure you don't run out of defined ranges; if that were to happen, you'd be storing all new data in one really large partition, which wouldn't be pleasant.
Thanks for wading through this long post. I hope the information contained proves informative and helpful. I'll be following this post with some actual code to help illustrate some of what I've covered here.
Regards,
Michelle


