sql server中filegroup與partition解析

文章來源:http://www.cnblogs.com/xwdreamer/archive/2012/08/30/2664671.html

 

sql server中filegroup與partition解析

0.參考文獻:

SQL SERVER 分區表的總結

SQL Server 2005 分區表實踐——建立分區表(partition table)

SQL Server中數據庫文件的存放方式,文件和文件組 (fromCareySon)

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語句來測試

View Code
 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 PagePAGELATCH資源瓶頸。在同一時間,只能有一個用戶插入一筆交易。

怎樣才能解決或者緩解這種瓶頸呢?

  1. 最簡單的方法,是換一個數據列建聚集索引,而不要建在Identity的字段上。這樣表格裏的數據就按照其他方式排序,同一時間的插入就有機會分散在不同的頁面上。
  2. 如果實在是一定要在Identity的字段上建聚集索引,建議根據其他某個數據列在表格上建立若干個分區(Partition)。把一個表格分成若干個分區,可以使得接受新數據的頁面數目增加。

還是以上面那個股票交易系統爲例子。不同的股票屬於不同的行業。開發者可以根據股票的行業屬性,將一張交易表分成若干個分區。在SQL Server裏,已分區表(Partitioned Table)的每個分區都是一個獨立的存儲單位。分屬不同分區的數據行是嚴格分開存儲的。所以同一個時間發生的交易記錄,因其行業不同,也會被分別保存在不同的分區裏。這樣,在同一個時間點,可以插入不同行業的交易記錄。每個分區上的Hot Page(接受新數據插入的page)就不那麼hot了。

在我的事例中,是有一張SalesOrderDetail表,其數據量很大,我希望按照UnitPrice這個字段進行分區。下面來看具體步驟。

step1:創建filegroup

在sql server中好像沒有create filegroup的說法,只是在現成的數據庫中添加filegroup而已。下面的代碼中首先創建數據庫,然後添加四個filegroup,tsql代碼如下所示:

View Code
--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語句如下圖所示:

View Code
--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 個分區。

創建分區函數的具體如法如下:

View Code
CREATE PARTITION FUNCTION partition_function_name ( input_parameter_type )
AS RANGE [ LEFT | RIGHT ] 
FOR VALUES ( [ boundary_value [ ,...n ] ] ) 
[ ; ]

在本事例中,需要有5個分區,本實例創建的分區函數如下所示:

View Code
--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如下所示:

View Code
--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:創建分區表並填充數據

View Code
--創建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來創建表結構:

View Code
--創建表結構
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':創建分區表並填充數據

在上面語句中,我們發現:

  1. 創建主鍵約束的時候我們指定使用的是nonclustered index,如果不指明的話那麼默認創建的是聚集索引。但是一張表只能有一個聚集索引,而分區列上又必須有聚集索引,所以我們這裏要顯式聲明primary key爲nonclustered index。
  2. 然後在UnitPrice列上面創建聚集索引,並且指明分區架構,也就是ON [Sch_SalesOrderDetail_UnitPrice]([UnitPrice])

在創建好表結構以後,我們往裏面插入數據。如果一條一條插入數據比較慢的話,我們可以在AdventureWorks2008R2.Sales.SalesOrderDetail表中導入,導入語句如下:

View Code
--插入數據,SalesOrderDetailID是標識列,identity約束
Insert into SalesOrderDetail(CarrierTrackingNumber,OrderQty,ProductID,SpecialOfferID,UnitPrice,UnitPriceDiscount) 
 select CarrierTrackingNumber,OrderQty,    ProductID,SpecialOfferID,UnitPrice,UnitPriceDiscount from AdventureWorks2008R2.Sales.SalesOrderDetail

6.查看分區表各分區數據情況(數據行數,最大最小 UnitPrice值)

執行如下查詢命令

View Code
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狀態的的四個視圖

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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章