天天看點

【譯】SQL Server索引進階第八篇:唯一索引

    索引設計是資料庫設計中比較重要的一個環節,對資料庫的性能其中至關重要的作用,但是索引的設計卻又不是那麼容易的事情,性能也不是那麼輕易就擷取到的,很多的技術人員因為不恰當的建立索引,最後使得其效果适得其反,可以說“成也索引,敗也索引”。

    唯一索引和其它索引本質上并沒有什麼不同,唯一不同的是唯一索引不允許索引鍵中存在相同的值。因為索引中每一個條目都與表中的行對應。唯一索引不允許重複值被插入索引也就保證了對應的行不允許被插入索引所在的表,這也是為什麼唯一索引能夠實作主鍵和候選鍵。

    為表聲明主鍵或唯一限制時,SQL Server會自動建立與之對應的唯一索引。你可以在沒有唯一限制的情況下建立唯一索引,但反之則不行。定義一個限制時,SQL Server會自動建立一個與之同名的索引,并且你不能在删除限制之前删除索引。但可以删除限制,删除限制也會導緻與之關聯的索引被删除。

    每個表中可以包含多個唯一索引。比如說AdventureWorks的Product表,含有四個唯一索引,分别是ProductID,ProductNumber,rowguid和ProductNameColumn,設定Product表的人将ProductID作為主鍵,其它三個作為候選鍵。

     你可以通過Create INDEX語句建立唯一索引,比如:

     也可以通過直接定義限制建立唯一索引:

    上面第一種方法,你Prodcut表中不能含有相同的ProductName,第二種情況表中不允許存在相同的ProductID。

    因為定義一個主鍵或是定義限制會導緻索引被建立,是以你必須在限制定義時就給出必要的索引資訊,是以上面ALTER TABLE語句中包含了”CLUSTERED”關鍵字。

    如果唯一索引或限制所限制的列在目前的表中已經含有了重複值,那麼建立索引會失敗。

    而當唯一索引建立成功後,所有違反這個限制的DML語句都會失敗,比如,我們打算加入一條目前表中存在的的ProductName,語句如下:

INSERT    Production.Product

                    ( Name ,

                      ProductNumber ,

                      Color ,

                      SafetyStockLevel ,

                      ReorderPoint ,

                      StandardCost ,

                      ListPrice ,

                      Size ,

                      SizeUnitMeasureCode ,

                      WeightUnitMeasureCode ,

                      [Weight] ,

                      DaysToManufacture ,

                      ProductLine ,

                      Class ,

                      Style ,

                      ProductSubcategoryID ,

                      ProductModelID ,

                      SellStartDate ,

                      SellEndDate ,

                      DiscontinuedDate

                    )

          VALUES    ( 'Full-Finger Gloves, M' ,

                      'A unique product number' ,

                      'Black' ,

                      4 ,

                      3 ,

                      20.00 ,

                      40.00 ,

                      'M' ,

                      NULL ,

                      0 ,

                      'U' ,

                      20 ,

                      GETDATE() ,

                      NULL

                    ) ;

代碼1.插入的行和表中存在相同的ProductName

上面代碼執行後我們可以看到如下報錯資訊:

上面的消息告訴我們AK_Product_Name索引不允許我們插入的資料含有和目前表中一樣的ProductName。

    主鍵限制和唯一限制有如下細小的差别。

    主鍵限制不允許出現NULL值。任何索引的索引鍵都不允許包含null值。但唯一限制允許包含NULL值,但唯一限制把兩個NULL值當作重複值,是以施加了唯一限制的每一列隻允許包含一個NULL值。

    建立主鍵時會自動建立聚集索引,除非目前表中已經含有了聚集索引或是建立主鍵時指定了NONCLUSTERED關鍵字。

    建立唯一限制時會自動建立非聚集索引,除非你指定了CLUSTERED關鍵字并且目前表中還沒有聚集索引。

    每個表中隻能有一個主鍵,但可以由多個唯一限制。

    對于唯一限制和唯一索引的選擇,請參照MSDN上的指導,如下:

    唯一限制和唯一索引并沒有顯著的差別。建立獨立的唯一索引和使用唯一限制對于資料的驗證方式并無差別。查詢優化器也不會區分唯一索引是由限制建立還是手工建立。然而以資料完整性為目标的話,最好建立限制,這使得對應的索引的目标一目了然。

     上面我們提到過唯一索引隻允許一個NULL值,但這和常見的業務需求有沖突。很多時候我們對于已經存在的值不允許重複,但是允許存在多個沒有值的列。

    比如說吧,你是一個供貨商,你所有的産品都來自于第三方廠商。你将你這裡所有的商品資訊都存在一個叫做ProductDemo的表中。你有自己的ProductID,還追蹤産品的UPC(Universal Product Code)值。但并不是所有的廠商産品都存在UPC,你表中的部分資料如下所示。

ProductID

UPCode

Other Columns

主鍵

唯一索引

14AJ-W

036000291452

23CZ-M

23CZ-L

18MM-J

044000865867

表1.ProductDemo表的部分内容

    在上表中第二列,你既要保證UPCode的唯一性,又要保證允許NULL值。實作這種需求最好的辦法就是混合唯一索引和過濾索引(過濾索引實在SQL Server 2008中引入的)。

    作為示範,我們建立了表1所示的表.

CREATE TABLE ProductDemo

                (

                  ProductID NCHAR(6) NOT NULL

                                     PRIMARY KEY ,

                  UPCode NCHAR(12) NULL

                ) ;

    接下來我們插入如上所示的資料.

    當我們插入重複值時

    收到如下錯誤

   (譯者注,這裡原文作者應該是疏忽了,略坑爹,因為他沒有建立過濾唯一索引,是以按照原文不會報錯,我在這裡加上了,代碼:CREATE UNIQUE NONCLUSTERED INDEX xx on ProductDemo(UPCode) where UPCode!=null)

    當你建立唯一索引時,你可以指定IGNORE_DUP_KEY選項,是以本文最開始建立唯一索引的選項可以是:

     IGNORE_DUP_KEY這個名字容易讓人誤會。唯一索引存在時重複的值永遠不會被忽略。更準确的說,唯一索引中永遠不允許存在重複鍵。這個選項的作用僅僅是在多列插入時有用。

    比如,你有兩個表,表A和表B,有着完全相同的結構。你可能送出如下語句給SQL Server。

    SQL Server會嘗試将所有表B中的資料插入表A。但如果因為唯一索引拒絕表B中含有和表A相同的資料插入A怎麼辦?你是希望僅僅重複資料插入不成功,還是整個INSERT語句不成功?

    這個取決于你設定的IGNORE_DUP_KEY參數,當你建立唯一索引時,通過設定設個參數可以設定當插入不成功時怎麼辦,設定IGNORE_DUP_KEY的兩種參數解釋如下:

    整個INSERT語句都不會成功并彈出錯誤提示,這也是預設設定。

    隻有那些具有重複鍵的行不成功,其它所有的行會成功。并彈出警告資訊。

    IGNORE_DUP_KEY 選項僅僅影響插入語句。而不會被UPDATE,CREATE INDEX,ALTER INDEX所影響。這個選項也可以在設定主鍵和唯一限制時進行設定。

    唯一索引可以提供出乎你意料之外的性能提升。這是因為唯一索引給SQL Server提供了確定某一列絕對沒有重複值的資訊。adventureWork的Product表中的ProductID和ProductName這兩個唯一索引,提供了很好的例子。

    加入,你們公司資料倉庫的某個哥們希望你給他提供Product表的一些資訊,要求如下:

    産品名稱

    産品銷售的數量

    總銷售額

    是以,你寫了如下的查詢語句:

    (譯者注,這裡原作者給的代碼有問題,ProductID替換為P.Name)

   資料倉庫的哥們對你的查詢語句很滿意,每一行都包含了産品名稱,銷售數量和總的銷售額,查詢出來的部分結果如下:

【譯】SQL Server索引進階第八篇:唯一索引

    但是,你對于這個查詢的成本有所擔心。SalesOrderDetail是上面查詢中兩個表中比較大的表,并且還按照ProductName進行分組,這個ProductName是來自Product表而不是SalesOrderDetail表。

    通過SQL Server Management Studio,你注意到SalesOrderDetail表有主鍵,并且主鍵也是聚集索引鍵,也就是SalesOrderID和SalesOrderDetailID,這個主鍵并不會給按照ProductName分組帶來性能提升。

    如果你運作了第五篇包含列的代碼,你建立了如下非聚集索引。

    你覺得這個索引可以對你的查詢有幫助因為這個索引包含了除了ProductName列的所有查詢所需的資訊。并且這個索引是按照ProductID進行排序的,但你仍然擔心分組的ProductID來自其中一個表而Select的資訊來自另一個表。

    你通過SQL Server Management Studio,通過檢視執行計劃,看到前面資料倉庫那哥們要的查詢的執行計劃如圖1所示。

【譯】SQL Server索引進階第八篇:唯一索引

圖1.按Product.Name進行分組時的執行計劃

    首先你可以驚訝于Product表的Product name索引,Product.AK_Product_Name沒有被使用.然後你意識到在Product.Name列上和Product.ProductID上有唯一索引,這使得SQL Server知道這兩列是唯一的。是以,Group By Name等效于Group By ProductID。這使得一個産品一個組。

    是以,查詢優化器意識到你的查詢等同于如下查詢,這兩個ProductID索引是以支援對所求查詢的Join和group操作。

    SQL Server會同時掃描SalesOrderDetail上的覆寫索引和聚集索引,這兩個索引都是以ProductID進行排序的。是以使用合并連接配接,而免去了排序或散列操作,總之SQL Server生成了最有效的查詢計劃。

    如果你Drop了Product.AK_Product_Name索引,比如:

    那麼生成的新的執行計劃就沒有那麼有效了,需要額外的排序和合并連接配接操作。

【譯】SQL Server索引進階第八篇:唯一索引

圖2.當Drop掉索引後,按照Product Name進行分組的查詢的執行計劃

    你可以看到,雖然唯一索引的主要功能是保證資料的完整性,還可以幫助查詢優化器生成更好的查詢計劃,即使這個索引本身不被用來通路資料。

    唯一索引為主鍵和候選鍵提供了限制。唯一索引可以在沒有唯一限制時存在,反之則不行。

     唯一索引同時也可以是過濾索引,這使得唯一索引可以允許一列中有多個NULL值。

    IGNORE_DUP_KEY 關鍵字可以影響批量插入語句。

    唯一索引還可以提供更好的性能,既然唯一索引本身并沒有用于資料通路。