Peter Larsson Blog

Patron Saint of Lost Yaks

Horizontal partitioning

Today I investigated the potential benefits of horizontal partitioning for a client using SQL Server 2000.
 
I profiled the client's queries to the database and found out that current quarter only is used in 72% of the queries, 21% of queries is current and previous quarter only and remaining 7% of queries is for any quarter.
 
Their current Order table has about 4.5 million records, and the average duration for a query is about 1200 ms. Every quarter has an average of 100000 records (about 11 years of data).
With this information at hand, I decided to partition the current order table in three;
 
·         Current quarter
·         Previous quarter
·         All other quarters
 
After creating the partitioning scheme in development area, the current quarter queries averaged at an exciting 112 ms, current and previous quarter averaged at 243 ms and average duration for all queries dropped to 1000 ms.
 
One thing happened during testing that I was not prepared for. I thought I was going to get an error when trying to update an order so that the check-constraint got violated. It didn't. Instead, SQL Server moved the record to the proper table!
 
With this new information, I can implement the horizontal partitioning seamlessly. Rename Order table to OrderCurr, create a new table OrderPrev and a third table OrderHist. I am going to create a partitioned view named Order, which means all code will continue to function.
 
For demonstration only, this is how I proceeded.
 
CREATE TABLE          Orders2000
                      (
                                 OrderID INT NOT NULL,
                                 CustomerID VARCHAR(15) NOT NULL,
                                 OrderDate DATETIME NOT NULL,
                                 CONSTRAINT PK_Orders2000
                                 PRIMARY KEY(
                                                       OrderDate,
                                                      OrderID
                                            ),
                                 CONSTRAINT UQ_Orders2000_OrderID
                                 UNIQUE     (
                                                       OrderID
                                            ),
                                 CONSTRAINT CHK_Orders2000_OrderDate
                                 CHECK      (
                                                       OrderDate >= '20000101'
                                                       AND OrderDate < '20010101'                                        )
                      )
 
CREATE TABLE          Orders2001
                      (
                                 OrderID INT NOT NULL,
                                 CustomerID VARCHAR(15) NOT NULL,
                                 OrderDate DATETIME NOT NULL,
                                 CONSTRAINT PK_Orders2001
                                 PRIMARY KEY(
                                                       OrderDate,
                                                       OrderID
                                            ),
                                 CONSTRAINT UQ_Orders2001_OrderID
                                 UNIQUE     (
                                                       OrderID
                                            ),
                                 CONSTRAINT CHK_Orders2001_OrderDate
                                 CHECK      (
                                                       OrderDate >= '20010101'
                                                       AND OrderDate < '20020101'
                                            )
                      )
 
CREATE TABLE          Orders2002
                      (
                                 OrderID INT NOT NULL,
                                 CustomerID VARCHAR(15) NOT NULL,
                                 OrderDate DATETIME NOT NULL,
                                 CONSTRAINT PK_Orders2002
                                 PRIMARY KEY(
                                                       OrderDate,
                                                       OrderID
                                            ),
                                 CONSTRAINT UQ_Orders2002_OrderID
                                 UNIQUE     (
                                                       OrderID
                                            ),
                                 CONSTRAINT CHK_Orders2002_OrderDate
                                 CHECK      (
                                                       OrderDate >= '20020101'
                                                       AND OrderDate < '20030101'
                                            )
                      )
 
I then created a partitioned view like this
 
CREATE VIEW vwAllOrders
AS
SELECT     OrderID, CustomerID, OrderDate FROM Orders2000
UNION ALL
SELECT     OrderID, CustomerID, OrderDate FROM Orders2001
UNION ALL
SELECT     OrderID, CustomerID, OrderDate FROM Orders2002
 
I inserted some test data
 
INSERT     vwAllOrders
(
                      OrderID,
                      CustomerID,
                      OrderDate
           )
SELECT     1,
           'Peso',
           '20011225'
UNION ALL
SELECT     2,
           'Jennie',
           '20020314'
 
For verification, I run this query
 
SELECT     'Orders2000 before update' AS Action, OrderID, CustomerID, OrderDate FROM Orders2000
UNION ALL
SELECT     'Orders2001 before update', OrderID, CustomerID, OrderDate FROM Orders2001
UNION ALL
SELECT     'Orders2002 before update', OrderID, CustomerID, OrderDate FROM Orders2002
 
Where I got this result
 
Action                           OrderID    CustomerID OrderDate
Orders2001 before update         1          Peso       2001-12-25
Orders2002 before update         2          Jennie     2002-03-14
 
Now I updated one of the orders
 
UPDATE     vwAllOrders
SET        OrderDate = '20000906'
WHERE      OrderID = 1
 
And ran this query for verification
 
SELECT     'Orders2000 after update' AS Action, OrderID, CustomerID, OrderDate FROM Orders2000
UNION ALL
SELECT     'Orders2001 after update', OrderID, CustomerID, OrderDate FROM Orders2001
UNION ALL
SELECT     'Orders2002 after update', OrderID, CustomerID, OrderDate FROM Orders2002
 
And got this result
 
Action                           OrderID    CustomerID OrderDate
Orders2000 after update          1          Peso       2000-09-06
Orders2002 after update          2          Jennie     2002-03-14
 
The order 1 has moved to proper table according to constraint without manual intervention!
If you are interested, check out the execution plans for this query
 
SELECT * FROM vwAllOrders
 

Legacy Comments


Mladen
2007-09-24
re: Horizontal partitioning
the beauty of partitioned views :)

Tara
2007-09-25
re: Horizontal partitioning
I'm surprised that horizontal partitioning would be considered for such a small table.

Peso
2007-09-25
re: Horizontal partitioning
It is not that the table has 19 columns of UNIQUEIDENTIFIER datatype.

It is that the table is queried (during the 1.5 hour measurement period) for 52000 times.
If I can reduce the average time for the query from 1.2 seconds to 0.1 seconds, there is much time saved during the day.

Kapil Gupta
2007-09-27
re: Horizontal partitioning
is it necessary to have PK on the partitioned key

Peter Larsson
2007-10-02
re: Horizontal partitioning
Yes. The primary key must exist of

1) The unique column
2) The check constraint

Bill
2010-04-15
re: Horizontal partitioning
Does the order of the columns in the primary key matter? I mean, can I put the column involved in the check constraint after the unique column(s)?

Peso
2010-04-15
re: Horizontal partitioning
No, the order of key columns doesn't matter for functionality.
For performance, the order may matter.