0.參考文獻:
SQL Server 2005 分區表實踐——建立分區表(partition table)
SQL Server中數據庫文件的存放方式,文件和文件組 (from CareySon)
T-SQL查詢進階--理解SQL SERVER中的分區表 (from CareySon)
1.基礎知識
一直對於表分區和filegroup的概念不是很清晰,今天通過具體的實例來學習什么事filegroup和partition,以及他們的作用。
1.1通過文件組來管理文件的特性
對於用戶角度來說,需對創建的對象指定存儲的文件組只有三種數據對象:表,索引和大對象(LOB)
使用文件組可以隔離用戶和文件,使得用戶針對文件組來建立表和索引,而不是實際磁盤中的文件。也就是可以指定將表和索引存儲在不同的文件上面。
使用文件組來管理文件可以使得同一文件組內的不同文件分布在不同的硬盤中,極大的提高了IO性能.
SQL SERVER會根據每個文件設置的初始大小和增長量會自動分配新加入的空間,假設在同一文件組中的文件A設置的大小為文件B的兩倍,新增一個數據占用三頁(Page),則按比例將2頁分配到文件A中,1頁分配到文件B中.
1.2文件的分類
- 首要文件:這個文件是必須有的,而且只能有一個。這個文件額外存放了其他文件的位置等信息.擴展名為.mdf
- 次要文件:可以建任意多個,用於不同目的存放.擴展名為.ndf,用於存放數據,而不是日志。
- 日志文件:存放日志,擴展名為.ldf
在SQL SERVER 2008之后,還新增了文件流數據文件和全文索引文件.
我們可以通過sys.database_files這個視圖查看數據庫中的文件情況:
select * from sys.database_files
1.3創建filegroup,並將索引創建在指定的filegroup中
可以通過TSQL語句來創建文件組,也可以通過SSMS來創建文件組,這個在后面會提到。這里不再重復。下面我們重點來介紹如何將索引創建在指定的filegroup中,而不跟數據放在一起。首先來看我創建好的filegroup,已經這些filegroup所對應的files,如下圖所示:
然后我們通過如下TSQL語句來測試

use TESTDB --step1.插入數據 select * into OrderDetail from AdventureWorks2008R2.Sales.SalesOrderDetail --step2:查看表的索引信息,發現所有頁都在pagefid=1上面,並且indexid都為0.因為沒有創建聚集索引之前是堆表 dbcc ind ( TESTDB, [dbo.OrderDetail], -1) --step3:在分區上創建聚集索引,聚集索引不要放在IndexStorage這個filegroup當中,因為聚集索引就是數據本身。 --如果將聚集索引on IndexStorage的話,那么所有數據都將會在IndexStorage這個filegroup所對應的文件上 create clustered index idx_c_SSalesOrderDetailID on OrderDetail(SalesOrderDetailID) --step4:此時發現原先indexid=0的都變成了index=1 dbcc ind ( TESTDB, [dbo.OrderDetail], -1) --step5:在IndexStorage這個file group上面創建非聚集索引 CREATE NONCLUSTERED INDEX idx_nc_SalesOrderID ON dbo.OrderDetail(SalesOrderID) on IndexStorage CREATE NONCLUSTERED INDEX idx_nc_CarrierTrackingNumber ON dbo.OrderDetail(CarrierTrackingNumber) on IndexStorage CREATE NONCLUSTERED INDEX idx_nc_UnitPrice ON dbo.OrderDetail(UnitPrice) on IndexStorage --step6:再次查看頁信息我們發現只有indexid=1的pagefid=1,也就是說聚集索引都在TESTDB.MDF這個文件上, --而indexid=2,3,4所對應的pagefid=3,表明已經將索引建立到IndexStorage這個filegroup上面去了,對應的是IndexStorage.ndf這個文件。 dbcc ind ( TESTDB, [dbo.OrderDetail], -1) --step7:創建復合索引, CREATE NONCLUSTERED INDEX idx_nc_com ON dbo.OrderDetail(SalesOrderID,CarrierTrackingNumber,UnitPrice) --step8:默認情況下會使用Primary這個filegroup,filefid=1. dbcc ind ( TESTDB, [dbo.OrderDetail], -1)
總結:
- 在分區上創建聚集索引,聚集索引不要放在IndexStorage這個filegroup當中,因為聚集索引就是數據本身。如果將聚集索引on IndexStorage的話,那么所有數據都將會在IndexStorage這個filegroup所對應的文件上。
- 在創建非聚集索引的時候,通過在創建索引語句的最后加上 on [filegroup]指定需要將這個索引放在哪一個filegroup當中,如果不加的話會使用默認filegroup,我們這里的默認filegroup是priamry。
1.4使用多個文件的好處
使用多個文件分布數據到多個硬盤中可以極大的提高IO性能.放在一個磁盤中基本沒有效果。
場景描述
應用程序發來大量的並發語句在修改同一張表格里的記錄,而表格架構設計以及用戶業務邏輯使得這些修改都集中在同一個頁面,或者數量不多的幾個頁面上。這些頁面有的時候也被稱為Hot Page。這樣的瓶頸通常只會發生在並發用戶比較多的、典型的OLTP系統上。這種瓶頸是無法通過提高硬件配置解決的,只有通過修改表格設計或者業務邏輯,讓修改分散到盡可能多的頁面上,才能提高並發性能。
在現實環境里,可以試想下面的情形。一個股票交易系統,每一筆交易都會有一個流水號,是遞增且不可重復的。而客戶發過來的交易請求,都要存儲在同一張交易表里。每一個新的交易,都要插入一條新記錄。如果設計者選擇在流水號上建聚集索引(這也是很自然的),就容易遇到Hot Page的PAGELATCH資源瓶頸。在同一時間,只能有一個用戶插入一筆交易。
怎樣才能解決或者緩解這種瓶頸呢?
- 最簡單的方法,是換一個數據列建聚集索引,而不要建在Identity的字段上。這樣表格里的數據就按照其他方式排序,同一時間的插入就有機會分散在不同的頁面上。
- 如果實在是一定要在Identity的字段上建聚集索引,建議根據其他某個數據列在表格上建立若干個分區(Partition)。把一個表格分成若干個分區,可以使得接受新數據的頁面數目增加。
還是以上面那個股票交易系統為例子。不同的股票屬於不同的行業。開發者可以根據股票的行業屬性,將一張交易表分成若干個分區。在SQL Server里,已分區表(Partitioned Table)的每個分區都是一個獨立的存儲單位。分屬不同分區的數據行是嚴格分開存儲的。所以同一個時間發生的交易記錄,因其行業不同,也會被分別保存在不同的分區里。這樣,在同一個時間點,可以插入不同行業的交易記錄。每個分區上的Hot Page(接受新數據插入的page)就不那么hot了。
在我的事例中,是有一張SalesOrderDetail表,其數據量很大,我希望按照UnitPrice這個字段進行分區。下面來看具體步驟。
step1:創建filegroup
在sql server中好像沒有create filegroup的說法,只是在現成的數據庫中添加filegroup而已。下面的代碼中首先創建數據庫,然后添加四個filegroup,tsql代碼如下所示:

--step1------ --創建數據庫 create database TEST USE MASTER GO --40萬行分成5個文件組,PRIMARY加下面四個文件組, --命名規則:FG_數據庫名_表名_字段名_流水號 ALTER DATABASE TEST ADD FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_1; ALTER DATABASE TEST ADD FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_2; ALTER DATABASE TEST ADD FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_3; ALTER DATABASE TEST ADD FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_4; GO
執行完以后我們可以在TEST數據庫的properties中看到我們添加的四個filegroup,如下圖所示:
step2:為filegroup添加數據文件
在創建完filegroup以后,我們為每一個filegroup創建一個次要數據文件,因為每一個數據庫只能有一個primary datafile,也就是mdf文件,但是可以有多個次要數據文件,也就是.ndf文件。為filegroup創建ndf數據文件的TSQL語句如下圖所示:

--step2------------------ USE TEST GO --給每個文件組加個次數據文件,也就是.ndf文件 --文件命名規則:文件組名_data_流水號 ALTER DATABASE TEST ADD FILE ( NAME=N'FG_TEST_SalesOrderDetail_UnitPrice_1_data_1', FILENAME=N'D:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG_TEST_SalesOrderDetail_UnitPrice_1_data_1.ndf', SIZE=2MB, FILEGROWTH=10% ) TO FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_1; ALTER DATABASE TEST ADD FILE ( NAME=N'FG_TEST_SalesOrderDetail_UnitPrice_2_data_1', FILENAME=N'D:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG_TEST_SalesOrderDetail_UnitPrice_2_data_1.ndf', SIZE=2MB, FILEGROWTH=10% ) TO FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_2; ALTER DATABASE TEST ADD FILE ( NAME=N'FG_TEST_SalesOrderDetail_UnitPrice_3_data_1', FILENAME=N'D:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG_TEST_SalesOrderDetail_UnitPrice_3_data_1.ndf', SIZE=2MB, FILEGROWTH=10% ) TO FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_3; ALTER DATABASE TEST ADD FILE ( NAME=N'FG_TEST_SalesOrderDetail_UnitPrice_4_data_1', FILENAME=N'D:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\FG_TEST_SalesOrderDetail_UnitPrice_4_data_1.ndf', SIZE=2MB, FILEGROWTH=10% ) TO FILEGROUP FG_TEST_SalesOrderDetail_UnitPrice_4; GO
執行完上述語句以后,我們可以查看TEST數據庫的file properties,如下圖所示:
我們可以看到四個文件的大小都是2MB。
step3:創建分區函數
在當前數據庫中創建一個函數,該函數可根據指定列的值將表或索引的各行映射到分區。 使用 CREATE PARTITION FUNCTION 是創建已分區表或索引的第一步。 在 SQL Server 2012 中,一張表或一個索引最多可以有 15,000 個分區。
創建分區函數的具體如法如下:

CREATE PARTITION FUNCTION partition_function_name ( input_parameter_type ) AS RANGE [ LEFT | RIGHT ] FOR VALUES ( [ boundary_value [ ,...n ] ] ) [ ; ]
在本事例中,需要有5個分區,本實例創建的分區函數如下所示:

--step3 --創建分區函數 --分區函數命名:fn_Partition_表名_字段 --40000 CREATE PARTITION FUNCTION fn_Partition_SalesOrderDetail_UnitPrice(money) AS RANGE RIGHT FOR VALUES(500,1000,1500,2000); GO
其中RANGE LEFT|RIGHT表示當間隔值由 數據庫引擎 按升序從左到右排序時,boundary_value [ ,...n ] 屬於每個邊界值間隔的哪一側(左側還是右側,就是等於號在哪一邊)。 如果未指定,則默認值為 LEFT。比如在我的分區函數中指定的間隔是(500,1000,1500,2000),並且是RANGE RIGHT,那么我的范圍就是
分區 | 1 | 2 | 3 | 4 | 5 |
值 | <500 | >=500 and <1000 | >=1000 and <1500 | >=1500 and <2000 | >=2000 |
step4:建分區架構
創建分區架構的TSQL如下所示:

--step4 --創建分區架構 --分區架構命名:Sch_表名_字段名 CREATE PARTITION SCHEME Sch_SalesOrderDetail_UnitPrice AS PARTITION fn_Partition_SalesOrderDetail_UnitPrice TO ([PRIMARY],[FG_TEST_SalesOrderDetail_UnitPrice_1],[FG_TEST_SalesOrderDetail_UnitPrice_2],[FG_TEST_SalesOrderDetail_UnitPrice_3],[FG_TEST_SalesOrderDetail_UnitPrice_4]); GO
從上述TSQL中我們可以發現,在創建分區架構的時候關聯了分區函數以及具體的5個filegroup。
注意:這里並不一定必須要求有5個filegroup,我們可以填寫相同的filegroup,但是需要填寫5次filegroup,因為有5個分區。
step5:創建分區表並填充數據

--創建SalesOrderDetail表 select * into SalesOrderDetail from AdventureWorks2008R2.Sales.SalesOrderDetail --修改分區屬性 ALTER TABLE SalesOrderDetail ADD CONSTRAINT PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID PRIMARY KEY (SalesOrderDetailID,UnitPrice) ON [Sch_SalesOrderDetail_UnitPrice]([UnitPrice])
需要注意的是分區列UnitPrice必須有唯一約束或者是聚集索引。所以在這里我創建聚集索引的時候將UnitPrice列也添加進去了。如果不講UnitPrice設為聚集索引,也就是讓此列唯一,那么在執行上述命令的時候會報如下錯誤:
Msg 1908, Level 16, State 1, Line 2
Column 'UnitPrice' is partitioning column of the index 'PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID'. Partition columns for a unique index must be a subset of the index key.
Msg 1750, Level 16, State 0, Line 2
Could not create constraint. See previous errors.
在執行完上面的操作以后我們再去看看ndf文件有沒有變化,如下圖所示,我們發現ndf文件大小有增長,這表明已經往這幾個分區中寫入了數據。
上邊的表結構是通過select * into語句來創建表的,這種方式沒有普遍性,下面我們通過create table來創建表結構:

--創建表結構 CREATE TABLE [SalesOrderDetail]( [SalesOrderDetailID] [int] IDENTITY(1,1) NOT NULL, [CarrierTrackingNumber] [nvarchar](25) NULL, [OrderQty] [smallint] NOT NULL, [ProductID] [int] NOT NULL, [SpecialOfferID] [int] NOT NULL, [UnitPrice] [money] NOT NULL, [UnitPriceDiscount] [money] NOT NULL, [LineTotal] AS (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0))), [rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL, [ModifiedDate] [datetime] NOT NULL, CONSTRAINT [PK_SalesOrderDetail_SalesOrderDetailID] PRIMARY KEY NONCLUSTERED([SalesOrderDetailID] ASC)--主鍵非聚集索引 ) --在分區列上創建聚集索引,並且引入分區架構 create clustered index IXC_SalesOrderDetail_UnitPrice on dbo.SalesOrderDetail(UnitPrice) ON [Sch_SalesOrderDetail_UnitPrice]([UnitPrice]) --在rowguid和ModifiedDate上面添加約束 ALTER TABLE [SalesOrderDetail] ADD CONSTRAINT [DF_SalesOrderDetail_rowguid] DEFAULT (newid()) FOR [rowguid] GO ALTER TABLE [SalesOrderDetail] ADD CONSTRAINT [DF_SalesOrderDetail_ModifiedDate] DEFAULT (getdate()) FOR [ModifiedDate] GO
step5':創建分區表並填充數據
在上面語句中,我們發現:
- 創建主鍵約束的時候我們指定使用的是nonclustered index,如果不指明的話那么默認創建的是聚集索引。但是一張表只能有一個聚集索引,而分區列上又必須有聚集索引,所以我們這里要顯式聲明primary key為nonclustered index。
- 然后在UnitPrice列上面創建聚集索引,並且指明分區架構,也就是ON [Sch_SalesOrderDetail_UnitPrice]([UnitPrice])。
在創建好表結構以后,我們往里面插入數據。如果一條一條插入數據比較慢的話,我們可以在AdventureWorks2008R2.Sales.SalesOrderDetail表中導入,導入語句如下:

--插入數據,SalesOrderDetailID是標識列,identity約束 Insert into SalesOrderDetail(CarrierTrackingNumber,OrderQty,ProductID,SpecialOfferID,UnitPrice,UnitPriceDiscount) select CarrierTrackingNumber,OrderQty, ProductID,SpecialOfferID,UnitPrice,UnitPriceDiscount from AdventureWorks2008R2.Sales.SalesOrderDetail
6.查看分區表各分區數據情況(數據行數,最大最小 UnitPrice值)
執行如下查詢命令

select partition = $partition.fn_Partition_SalesOrderDetail_UnitPrice(UnitPrice) ,rows = count(*) ,minval = min(UnitPrice) ,maxval = max(UnitPrice) from dbo.SalesOrderDetail group by $partition.fn_Partition_SalesOrderDetail_UnitPrice(UnitPrice) order by partition
其中fn_Partition_SalesOrderDetail_UnitPrice(UnitPrice)是分區函數,UnitPrice是列名。
使用第一種方法,也就是select * into的方法導入數據,其查詢結果為:
partition rows minval maxval ----------- ----------- --------------------- --------------------- 1 88053 1.3282 469.794 2 12243 539.99 986.5742 3 9582 1000.4375 1466.01 4 939 1700.99 1971.9942 5 10500 2024.994 3578.27
我們可以看到每一個分區上面都有數據。有些總數據量小於2MB,所以ndf文件大小沒有改變。如果ndf文件文件大小變化不大,我們可以多執行幾次上面的數據導入語句。
使用第二種方法插入數據,一共執行了三次,其最后文件大小如下圖所示:
分區上存儲的數據統計信息如下:
partition rows minval maxval ----------- ----------- --------------------- --------------------- 1 264159 1.3282 469.794 2 36729 539.99 986.5742 3 28746 1000.4375 1466.01 4 2817 1700.99 1971.9942 5 31500 2024.994 3578.27
從上述查詢結果我們可以發現在partition4(對應FG_TEST_SalesOrderDetail_UnitPrice_3_data_1.ndf這個次要數據文件)中,數據行只有2817條,這也說明了為什么在上圖中只有FG_TEST_SalesOrderDetail_UnitPrice_3_data_1.ndf這個文件大小沒有增長,依然是2048KB。
查看partition狀態的的四個視圖

--查看partition的四個視圖 select * from sys.partition_functions--查看分區函數 select * from sys.partition_parameters select * from sys.partition_range_values--查看分區函數對應的分區范圍 select * from sys.partition_schemes--查看分區架構