浅谈SQL Server表分区及其应用

by shinichi_wtn 2016-08-08 22:20

SQL Server表分区简介

直接引用官网对于表分区的解释如下

SQL Server supports table and index partitioning. The data of partitioned tables and indexes is divided into units that can be spread across more than one filegroup in a database. The data is partitioned horizontally, so that groups of rows are mapped into individual partitions. All partitions of a single index or table must reside in the same database. The table or index is treated as a single logical entity when queries or updates are performed on the data.

--摘自https://msdn.microsoft.com/en-us/library/ms190787.aspx

SQL Server表分区的优势

1) You can transfer or access subsets of data quickly and efficiently, while maintaining the integrity of a data collection. For example, an operation such as loading data from an OLTP to an OLAP system takes only seconds, instead of the minutes and hours the operation takes when the data is not partitioned.

2) You can perform maintenance operations on one or more partitions more quickly. The operations are more efficient because they target only these data subsets, instead of the whole table. For example, you can choose to compress data in one or more partitions or rebuild one or more partitions of an index.

3) You may improve query performance, based on the types of queries you frequently run and on your hardware configuration. For example, the query optimizer can process equi-join queries between two or more partitioned tables faster when the partitioning columns in the tables are the same, because the partitions themselves can be joined.

--摘自https://msdn.microsoft.com/en-us/library/ms190787.aspx

从上述的优势可以看到,其对用户是透明的,但是在底层应用了数据分区,根据分区定义对表的数据进行了划分(比如把不同的分区放到不同的FileGroup),从而能够实现快速地数据装载/移动/删除。

SQL Server表分区应用

案例分析

设想一下我们有一个数据仓库的应用,每天会把新一天的数据加载到表FactTable中,这个数据量往往很大。

  -如果不应用表分区,那我们需要在同一个表FactTable里进行数据的插入操作,而插入操作往往会锁定整个表,这时往往会阻碍其他用户的查询,以及查询性能。同理,删除某天的数据也会非常慢并且阻碍用户的查询。

  -如果应用表分区,那我们可以对FactTable按date进行分区。在插入数据的时候,新建一个临时表FactTable_Staging和原表拥有相同的字段、索引以及分区scheme,我们先往FactTable_Staging里插入数据,然后通过Partition Switch将FactTable_Staging里新装载的分区直接switch到FactTable对应的分区即可。而switch的操作非常快,所以对于用户查询几乎不会有任何影响。同理,删除数据也可以应用类似的思路,通过创建临时表FactTable_Deleting将FactTable需要删除的分区switch到FactTable_Deleting中,然后Drop掉FactTable_Deleting即可完成删除操作。

接下来,我会用一个简单的例子,来展示表分区实现数据装载和删除的过程。

案例实现

我们首先创建一个测试数据库TestDB。

CREATE DATABASE [TestDB]
 ON  PRIMARY 
( NAME = N'TestDB', FILENAME = N'D:\Databases\TestDB.mdf' , SIZE = 4096KB , FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'TestDB_log', FILENAME = N'D:\Databases\TestDB_log.ldf' , SIZE = 1024KB , MAXSIZE = 102400KB , FILEGROWTH = 10%)
GO

接着我们创建一个Parition Function。Partition Function定义了分区的结构,其中Range Left和Range Right的区别在于边界的不同,一个是左边界,一个是右边界。

CREATE PARTITION FUNCTION pfDaily (date) 
AS RANGE LEFT FOR VALUES('2016-08-01','2016-08-02','2016-08-03','2016-08-04','2016-08-05','2016-08-06','2016-08-07')
GO

  -对于RANGE LEFT,第一个分区是所有小于或等于'2016-08-01'的数据,第二个分区是大于'2016-08-01'并且小于等于'2016-08-02'的数据,以此类推

  -对于RANGE RIGHT,第一个分区是所有小于'2016-08-01'的数据,第二个分区是大于等于'2016-08-01'并且小于'2016-08-02'的数据,以此类推

假设我们的Partition Function里面有n个点(Q1, Q2, ..., Qn),那么将会创建n+1个分区(P0,P1,P2, ..., Pn),对于RANGE LEFT,有P0<=Q1<P1<=Q2<P2<=Q3<...<Pn-1<=Qn<Pn,对于RANGE RIGHT,有P0<Q1<=P1<Q2<=P2<Q3<=...<=Pn-1<Qn<=Pn。对于这里的例子,一共有8个分区,8月1号及以前是分区1,8月2号是分区2,...,8月7号是分区7,8月8号及之后是分区8。

然后,我们基于刚才创建好的pfDaily来创建Partition Scheme,Parition Function只定义了分段函数,而Partition Scheme则是将分区映射到具体的FileGroup。为了简化示例,这里只使用默认的Primary FileGroup。

CREATE PARTITION SCHEME psDaily AS PARTITION pfDaily ALL 
TO ([primary])
GO

可以通过如下命令在系统表里查看已经创建的分区情况:

SELECT * FROM sys.partition_range_values
WHERE function_id IN (SELECT function_id
      From sys.partition_functions
      WHERE name IN ('pfDaily'))

有了Partition Scheme之后,我们就可以创建表了。这里创建了一个只有三列的示例表metric。

CREATE TABLE [dbo].[metric] (
   [id] int NOT NULL ,
   [date] date NULL ,
   [metric] int NULL
) ON psDaily ([date]) 
GO

数据装载

假设现在我们要往metric表里面添加一些2016-08-01的数据,传统的方式当然就是用INSERT或者BULK INSERT了。这里会利用partition switch来完成数据的装载操作。

Step1. 创建一个临时表metric_Staging,该表和metric表具有一模一样的结构

CREATE TABLE [dbo].[metric_Staging] (
   [id] int NOT NULL ,
   [date] date NULL ,
   [metric] int NULL
) ON psDaily ([date]) 
GO

Step2. 在临时表中插入数据

INSERT INTO metric_Staging([id],[date],[metric]) VALUES('1','2016-08-01','1')
INSERT INTO metric_Staging([id],[date],[metric]) VALUES('2','2016-08-01','2')
INSERT INTO metric_Staging([id],[date],[metric]) VALUES('3','2016-08-01','3')
INSERT INTO metric_Staging([id],[date],[metric]) VALUES('4','2016-08-01','4')
INSERT INTO metric_Staging([id],[date],[metric]) VALUES('5','2016-08-01','5')
INSERT INTO metric_Staging([id],[date],[metric]) VALUES('6','2016-08-01','6')

Step3. 将临时表metric_Staging的第一个分区switch到metric表的第一个分区

ALTER TABLE [metric_Staging] 
SWITCH PARTITION 1 
TO [metric] PARTITION 1

Step4. 删除临时表

DROP TABLE [metric_Staging]

如此一来,所有的数据装载操作都再metric_Staging里完成,最后再将metric_Staging里的分区直接切换到metric表里作为metric表的一个分区。

在运用partition switch的时候,我们需要知道需要装载数据的分区序号,这里是因为我们知道2016-08-01是第一个分区,在实际应用中,我们很可能需要动态获得分区序号,这也是非常容易实现的。

SELECT $Partition.pfDaily('2016-08-01') AS PartitionNumber

上面的SQL回返回1,也就是说2016-08-01对应了分区1。

数据删除

假设现在我们要把metric表里的2016-08-01的数据删除,传统的方式当然就是用DELETE FROM [metric] WHERE [date] = '2016-08-01'。这里同样利用partition switch来完成数据的删除操作。

--Step1. Create temp table
CREATE TABLE [dbo].[metric_Deleting] (
   [id] int NOT NULL ,
   [date] date NULL ,
   [metric] int NULL
) ON psDaily ([date]) 
GO
 
--Step2. Switch parition to be deleted into temp table
ALTER TABLE [metric] 
SWITCH PARTITION 1 
TO [metric_Deleting] PARTITION 1
 
--Step3. Drop temp table
DROP TABLE [metric_Deleting] 

Partition Merge/Partition Split

在现实应用中,表的分区不会一直不变,而是随着时间的推移动态变化的。比如在数据仓库应用中,我们只希望存放近一年的数据,而更老的数据我们会删除或者放在另一个表中。这就需要在新数据到来时更新分区函数,让最后一个分区分离(Split)成两个分区,让第一个分区和第二个分区合并。

假设2016-08-08的数据到来了,我们希望这些数据放在属于自己的分区中,这时,我们需要在分区函数中增加2016-08-08这个点,将以前(2016-08-07, 无穷大)的分区变成两个分区(2016-08-07, 2016-08-08]和(2016-08-08, 无穷大)。如此以来,2016-08-08的数据会落在(2016-08-07, 2016-08-08],这会保持最后一个分区永远是空(在该场景中,永远保持最后一个分区为空的好处是在Split的时候不需要做任何数据转移,从而提高性能)

--Split partition 2016-08-08
ALTER PARTITION SCHEME psDaily NEXT USED [PRIMARY]
ALTER PARTITION FUNCTION pfDaily() SPLIT RANGE ('2016-08-08')

同理,当老的分区不再需要,我们需要合并分区。比如我们已经删除了2016-08-01的数据,这时分区(无穷小, 2016-08-01]已经为空,我们希望删除2016-08-01这个点,从而(无穷小, 2016-08-01]与(2016-08-01, 2016-08-02]合并为(无穷小, 2016-08-02]。

--Mege partition 2016-08-01
ALTER PARTITION FUNCTION pfDaily() MERGE RANGE ('2016-08-01')

总结

掌握了基本的表分区知识后,我们就可以创建非常易于维护的表,新数据加载,旧数据移除,都可以在不干扰主表的基础上完成。当然,表分区也有一些限制,比如只有SQL Server Enterprise版本才有此功能;另外,分区数也是有限的,在SQL Server 2016里面最多之处15000个分区。

怎样决定该不该使用表分区呢?

可以参考此文:https://www.brentozar.com/archive/2012/03/how-decide-if-should-use-table-partitioning/,对于了解表分区会非常收益。

(仅用于Gavatar)

  Country flag

biuquote
  • Comment
  • Preview
Loading

About

shinichi_wtnI'm Shinichi_wtn

Software Engineering Manager at Microsoft

[More...]


Month List