一、前言
我已經在高興對服務器創建了表分區並且獲得良好性能和自動化管理分區切換的時候,某一天,開發人員告訴我,某表的兩個字段的數據不唯一,需要為這兩個字段創建唯一索引的時候,這一切就變得不完美了。
列的唯一,這個實際上是一個唯一索引。使用關鍵字unique建立。
二、背景
我有一個表TestUnique,這個表使用分區方案[Sch_TestUnique_Id],它是以Id做為分區依據列的,這個Id也是一個聚集索引,表中其它索引是跟分區對齊的(創建其它非聚集索引的時候使用了分區方案或者不指定-默認就是分區方案),而且我我這個表很大,我需要定時的進行交換分區(SWITCH PARTITION、滑動窗口、切換分區),表分區的相關信息可參考:SQL Server 表分區實戰系列(文章索引)
--創建測試表 CREATE TABLE [dbo].[TestUnique]( [Id] [int] IDENTITY(600000000,1) NOT FOR REPLICATION NOT NULL, [SiteId] [int] NULL, [Url] [nvarchar](420) NULL, [PublishOn] [datetime] NULL, [AddOn] [datetime] NULL, CONSTRAINT [PK_Archive] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = ON, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [Sch_TestUnique_Id]([Id]) ) ON [Sch_TestUnique_Id]([Id]) GO
現在需要創建SiteId+Url做為一個唯一索引,本來以為這個唯一索引是可以進行分區對齊的,但是卻在創建索引的時候遇到錯誤了。
三、分析
1. 對分區表創建索引時,SQL Server 將使用與該表相同的分區方案和分區依據列自動對索引進行分區。因此,索引的分區方式實質上與表的分區方式相同。這將使索引與表“對齊”。創建唯一索引有下面三種方式:
--方式1 CREATE UNIQUE NONCLUSTERED INDEX [IX_TestUnique_SiteIdUrl] ON [TestUnique] (SiteId,Url)
消息1908,級別16,狀態1,第1 行
列'Id' 是索引'IX_TestUnique_SiteIdUrl' 的分區依據列。唯一索引的分區依據列必須是索引鍵的子集。
--方式2 ALTER TABLE [dbo].[TestUnique] ADD CONSTRAINT [IX_TestUnique_SiteIdUrl] UNIQUE NONCLUSTERED ( [SiteId] ASC, [Url] ASC )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [Sch_TestUnique_Id]([Id])
消息1908,級別16,狀態1,第1 行
列'Id' 是索引'IX_TestUnique_SiteIdUrl' 的分區依據列。唯一索引的分區依據列必須是索引鍵的子集。
消息1750,級別16,狀態0,第1 行
無法創建約束。請參閱前面的錯誤消息。
--方式3 CREATE UNIQUE NONCLUSTERED INDEX [IX_TestUnique_SiteIdUrl] ON [dbo].[TestUnique] ( [SiteId] ASC, [Url] ASC )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = ON, ONLINE = OFF) ON [Sch_TestUnique_Id]([Id]) GO
--測試沒有指定分區方案時是否默認使用分區方案 CREATE NONCLUSTERED INDEX [IX_TestUnique_SiteIdUrl] ON [dbo].[TestUnique] ( [SiteId] ASC, [Url] ASC )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) GO
2. 如果分區依據列不可能包含在唯一鍵中,則必須使用 DML 觸發器,而不是強制實現唯一性。(在需要分區的表中,估計插入的數據量還是比較大的,在這個表使用觸發器應該會有性能上的問題吧?)
--測試索引鍵的子集 CREATE UNIQUE NONCLUSTERED INDEX [IX_TestUnique_SiteIdUrl] ON [dbo].[TestUnique] ( [Id] ASC, [SiteId] ASC, [Url] ASC )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = ON, ONLINE = OFF) ON [Sch_TestUnique_Id]([Id])
上面這條SQL是能成功執行的,不過從業務邏輯上來看,加了唯一的Id值對唯一就沒有任何意義了,但是這條SQL告訴我們:當你使用了SiteId做為分區依據列,那么你就可以創建以SiteId+Url的唯一索引。
3. IGNORE_DUP_KEY = ON與IGNORE_DUP_KEY = OFF的區別:
忽略重復鍵,在創建或修改唯一索引時,可以可設置一個忽略重復鍵的選項。如果此選項已設置為“是”(ON),當您試圖通過添加影響多行的數據來創建重復鍵(使用 INSERT 語句)時,則不會添加包含重復項的行,不重復的記錄會給插入到表中的;如果此選項設置為“否”(OFF),則整個插入操作將失敗,並且將回滾所有數據。
4. 如果您預計將通過使用更多分區來擴展索引,或者將會涉及到頻繁的分區切換,那么將索引與已分區表對齊將非常重要。有關詳細信息,請參閱設計分區以管理數據子集。如果表與其索引對齊,SQL Server 則可以快速高效地切換分區,同時又能維護表及其索引的分區結構。
5. 在下列情況下,獨立於基表而單獨設計已分區索引(不對齊)很有用:
- 基表未分區。
- 索引鍵是唯一的,不包含表的分區依據列。
- 您希望基表與使用不同聯接列的多個表一起參與組合聯接。
四、注意
1. 索引要與其基表對齊,並不需要與基表參與相同的命名分區函數。但是,索引和基表的分區函數在實質上必須相同,即:1) 分區函數的參數具有相同的數據類型;2) 分區函數定義了相同數目的分區;3) 分區函數為分區定義了相同的邊界值。
2. 若要啟用分區切換,表的所有索引都必須對齊。
3. 如果在創建時指定了不同的分區方案或單獨的文件組來存儲索引,則 SQL Server 不會將索引與表對齊。
五、總結
1. 如果不需要進行交換分區的情況下,並且你那么幸運讓唯一索引列包含了分區依據列的話,你完全可以讓唯一與表分區對齊,而且不用擔心交換分區的影響;
2. 如果不需要進行交換分區的情況下,唯一索引不包含分區依據列,那就讓唯一索引單獨使用一個文件組,這樣性能也能得到一部分的提升;
3. 如果需要進行交換分區的情況下,唯一索引不包含分區依據列,那就讓唯一索引單獨使用一個文件組,但是你需要在進行交換分區之前:停止TCP/IP防止進數據,重啟服務,刪除唯一索引,交換分區,創建唯一索引,啟用TCP/IP,重啟服務;(貌似這不是個好方法,歡迎大家提供好的方案)
六、參考文獻
->已分區索引的特殊指導原則(唯一索引)