使用GUID作為數據庫主鍵的測試

作者: dragonpro  來源: 博客園  發布時間: 2008-12-14 16:22  閱讀: 7977 次  推薦: 0   原文鏈接   [收藏]  

今天聽了MSDN的WebCast,是關于Entlib的數據訪問的講座,末尾我問了兩個自己所關心的問題:

  1. 在一個較大型的應用中,如果需要用到兩套以上的數據庫(如:SQL Server和Oracle),是否可以把需要的sql查詢全部封裝在存儲過程里,這樣就只需要一套訪問代碼了,有沒有更好的方法解決這個問題?
  2. 在數據庫的主鍵的設立中(同時支持多種數據庫)直接用GUID作為主鍵來得簡單,但是在查詢的時候影響性能的因素大不大,還有沒有更好的解決方法?

以上兩個問題,由于時間的關系吧,微軟的工程師解答的比較簡略,第一個應該需要針對具體的應用來考慮,但是第二個問題,性能影響肯定是有的,但是影響大不大呢,帶著這個問題,我做了這個小試驗。

注:如果您有更好的建議不防貢獻出來大家探討探討^_^!

測試環境:

  • Dell筆記本電腦 迅馳1.5G
  • Win XP professional
  • 512MB DDR RAM
  • SQL Server 2000 個人版

測試方法:

  • 建立有10個字段的數據庫[test_GUID],使用GUID作為主鍵,以及其他常用的字段類型,模擬現實中的使用情況,建表的SQL代碼如下:

    CREATE TABLE [dbo].[Test_GUID] (
     [GUID] [varchar] (50) COLLATE Chinese_PRC_CI_AS NOT NULL ,
     [test1] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test2] [datetime] NULL ,
     [test3] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test4] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
     [test5] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
     [test6] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test7] [text] COLLATE Chinese_PRC_CI_AS NULL ,
     [test8] [int] NULL ,
     [test9] [int] NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    GO

    ALTER TABLE [dbo].[Test_GUID] WITH NOCHECK ADD
     CONSTRAINT [PK_Test_GUID] PRIMARY KEY  CLUSTERED
     (
      [GUID]
     )  ON [PRIMARY]
    GO

  • 建立有10個字段的數據庫[test_IIDD],使用IIDD作為主鍵,以及其他常用的字段類型,模擬現實中的使用情況,建表的SQL代碼如下:

    CREATE TABLE [dbo].[Test_IIDD] (
     [IIDD] [numeric] (9) IDENTITY(1,1) NOT NULL ,
     [test1] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test2] [datetime] NULL ,
     [test3] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test4] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
     [test5] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
     [test6] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
     [test7] [text] COLLATE Chinese_PRC_CI_AS NULL ,
     [test8] [int] NULL ,
     [test9] [int] NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    GO

    ALTER TABLE [dbo].[Test_IIDD] WITH NOCHECK ADD
     CONSTRAINT [PK_Test_IIDD] PRIMARY KEY  CLUSTERED
     (
      [IIDD]
     )  ON [PRIMARY]
    GO

  • 可以看到,第一個表使用全局唯一標識(GUID)來作為主鍵,而第二個表使用普通numeric(類似Int型)的數據類型來作為主鍵,關于GUID這里做一個小小介紹:
    GUID,全局唯一標識,常用在COM組件的標識里,因為此幾乎不可能生成重復的兩個值,所以在各個領域經常用到,具體的值如:“A89C9547-032B-4860-ABB5-6EAEAVE934D5”所示,你一定看到過類似的字符串吧,^_^,在SQL Server2000 中使用newid()函數來獲取一個唯一的GUID
  • 分別運行如下兩個SQL語句對兩個表分別插入10萬條語句,我所關心大數據量的情況下的效果,所以不要怪我開始點選擇10萬條數據的情況^_^。

    declare @num int
    set @num = 0
    while(@num < 100000)
    begin

    insert into test_Guid
    values(
    newid(),
    'X222222222222222222',
    getdate(),
    'AAAAAAAAAAAAAAAAAA',
    'BBBBBBBBBBBBBBBB',
    'CCCCCCCCCCCCCCCCCCCCCC',
    'DDDDDDDDDDDDDDDDD',
    '479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    ',
    '1',
    '0'
    )

    set @num = @num+1
    end

    declare @num int
    set @num = 0
    while(@num < 100000)
    begin

    insert into test_IIDD
    values(
    'X222222222222222222',
    getdate(),
    'AAAAAAAAAAAAAAAAAA',
    'BBBBBBBBBBBBBBBB',
    'CCCCCCCCCCCCCCCCCCCCCC',
    'DDDDDDDDDDDDDDDDD',
    '479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    479C8AAD-3040-4FC5-B53A-D6AF085AD38A
    ',
    '1',
    '0'
    )

    set @num = @num+1
    end

  • 開始測試,測試代碼及顯示結果如下:
    #測試一 (GUID)

    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select * from test_guid
    where
    guid='A89C9547-032B-4860-ABB5-6EAEA0E934D5' or
    guid='FFFA8619-BC9F-4B76-ACE8-B3324105BBDE' or
    guid='FFFC26D5-6ECF-479D-838D-0D3E23AC7D2D' or
    guid='FFF9FA53-E115-450A-A52D-B0AET36FF539' or
    guid='A89C9547-032B-4860-ABB5-6EAEAVE934D5' or
    guid='FFF90A0B-CB5B-446F-81FC-CFA661D03CF8' or
    guid='FFF85F4A-4554-491F-9D1A-05C8BA3C1266' or
    guid='FFFF354A-ED3E-4C3A-A033-3406F229EB34'
    order by guid desc

    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------

    0秒,0毫秒,有時會有10毫秒的情況
    #測試二 (IIDD)

    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select * from test_IIDD
    where
    IIDD='1' or
    IIDD='2' or
    IIDD='200' or
    IIDD='8000' or
    IIDD='8900' or
    IIDD='3' or
    IIDD='8' or
    IIDD='10000'
    order by IIDD desc

    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------

    0秒,0毫秒,有時會有10毫秒的情況
  • 可以看到在10萬條數據的情況下,普通Select查詢的時候效率影響還不大
    #測試三 (GUID)
    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select count(*) from test_guid
    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------
    29秒,28793毫秒,效果不好啊!
    #測試四(IIDD)
    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select count(*) from test_IIDD
    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------
    第一次運行3秒,第二次運行1秒,第三次運行0秒,50毫秒,my god!
  • 這可如何是好,GUID在沒有where子句的聚合運算時吃大虧了
    #測試五 (GUID)
    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select count(*) from test_guid
    where
    test2 > '2005-06-03 21:05:33.330'
    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------
    29秒,29093毫秒,盡管查詢出來只有200多條數據但速度沒有變化!
    #測試六(IIDD)
    --------------------
    declare @times  datetime
    set @times = getdate()
    --------------------
    select count(*) from test_IIDD
    where
    test2 > '2005-06-03 21:05:33.330'
    ---------------------
    select datediff(second,@times,getdate()) as 秒,datediff(ms,@times,getdate()) as 毫秒
    ---------------------
    第一次運行2秒,第二次運行0秒,160毫秒,比沒有Where的情況稍慢
  • 如結果所示,效果很不理想
    #測試七 (GUID)
    把test_GUID這個表的test2這一列(datetime)添加為索引列

    運行【測試三】0秒,50毫秒,原來如此。。。

    運行【測試五】0秒,0毫秒,非常明顯了吧。

    #測試八(IIDD)
    把test_IIDD這個表的test2這一列(datetime)添加為索引列

    運行【測試四】0秒,40毫秒

    運行【測試六】0秒,40毫秒

  • 上面的測試七和測試八在返回值方面不盡相同造成一些微小的差別這個可以忽略(因為我測試了在相同返回值的情況下差別是很小的)
  • 可以看出在以GUID作為主鍵的表中加一個時間類型或是Int類型的索引可以彌補以GUID作為主鍵帶來的性能損失。

總結:

此次測試由于時間的關系,測試的比較片面也很膚淺,還望能有高手把不足和疏漏的地方進行補充和改進,在這次測試后我想我還會做更多的關于性能方面的測試,有精力再做吧。

此次測試就只得出這么一點膚淺的東西,希望沒有浪費您寶貴的時間^_^!

精彩評論:

呂震宇:

我想這個測試還存在一些問題,不是三言兩語能說清楚的。挑幾個我認為比較關鍵的說一說:

1、設計表時為什么用[GUID] [varchar] (50) ,是否出于兼容Oracle考慮?SQL Server中有UniqueIdentifier類型。 

2、測試結論有問題“在以GUID作為主鍵的表中加一個時間類型或是Int類型的索引可以彌補以GUID作為主鍵帶來的性能損失”在SQL Server中,如果在一個有聚簇索引的表上再建立其它索引,那么其它索引鏈接的就不是頁節點了,而是聚簇索引節點。也就是說,一個普通索引上的查詢先檢索普通索引,然后索引會告訴你對應數據的聚簇索引是什么,然后聚簇索引再告訴你數據再哪里。(可以參考微軟SQL Server培訓教程)。不過這并不是問題的關鍵。關鍵在下面: 

3、在上面的測試中,測試命令是:select count(*) from ... where test2 > '2005-06-03 21:05:33.330'。問題發生在了count(*)上面。這里的查詢只是計數,因此我們管它叫做索引覆蓋查詢,也就是只查時間索引就可以得到計數值,聚簇索引根本沒有派上用場,也就是說根本沒有比較聚簇索引的效率,所以你得到了速度一致的結論。這里,測試設計上有問題。你可以試試select *替換select count(*) ,我想結果差異應當非常明顯。關于索引可以參考http://www.cnblogs.com/zhenyulu/articles/25794.html

希望樓主再實驗一下。

dragonpro:   我非常想用GUID做主鍵用在我們開發的系統里面,但是這涉及到的問題也是需要充分考慮的,為了這些問題,特別是性能問題,我都考慮很久了,希望能有個滿意的處理方式,我的系統希望支持至少兩種數據庫,特別需要支持Oracle。
  但在做表的時候,如果在表里不使用另外的非聚集索引,我想很多查詢都會比較慢,那就比較可怕了。

  又做了下測試,用UniqueIdentifier類型的話跟Int型的在查詢方面相差不大,但是用varchar類型者需要在其他字段建立非聚集索引來為查詢優化創造條件,不知道這樣認為是否合適。
  再有,在插入數據的時候如果GUID字段為聚集索引的話,由于字段值是隨機的,我插入的數據并不知道要放在什么位置,這樣是否也需要選擇新記錄插 入的位置而消耗操作時間,所以我想索性指定一個日期型字段來作為聚集索引,這樣增加記錄基本上都是在末尾,這樣是否能有效減少了數據操作時間呢?
呂震宇: 非常佩服樓主的敬業精神。我還想說兩句,不知樓主是否贊同我的看法:
1、"看來用GUID作為主鍵必須要另外加索引才能保證入count這樣的計算不至于消耗太多時間",在這里另外的索引必須是你的Where短語中用到的字段,否則是不會帶來性能提升的。
2、“我插入的數據并不知道要放在什么位置,這樣是否也需要選擇新記錄插入的位置而消耗操作時間”,我以前也一直是這么想的,但感覺自己想法有問 題。我猜測加入GUID的聚簇索引主鍵時不會為選擇新的插入位置消耗太多的時間。因為聚簇索引的頁節點是數據節點,因此完全可以在枝節點上做文章以減少系 統的消耗。這只是我的猜測。所以用GUID與用時間做聚簇索引性能應當差別不大。當然這也是我的猜測。
3、我不太贊同用時間做聚集索引,說不出為什么,感覺不太好。似乎沒有做到“專職專責”。
0
0
 
標簽:主鍵 GUID
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()