使用有序GUID:提升其在各数据库中作为主键时的性能
原文出處:https://www.codeproject.com/articles/388157/guids-as-fast-primary-keys-under-multiple-database?,避免今后忘記了再去閱讀原英文。【】是感覺理解有問題的地方
正確的使用有序GUID在大部分?jǐn)?shù)據(jù)庫中可以獲得和 整型作為主鍵 時(shí)相媲美的性能。
介紹
這篇文章概述了一種方法去規(guī)避 當(dāng)使用GUID作為主鍵/聚焦索引時(shí)一些常見的弊端,借鑒了 Jimmy Nilsson 的文章 GUID作為主鍵的成本?。盡管這一基本實(shí)現(xiàn)已被集成在眾多庫和框架中(包括 NHibernate,譯者注:ABP框架中集成有 SequentialGuidGenerator),但大多數(shù)只針對 Microsoft SQL Server。這里將嘗試適配其他一些常見的數(shù)據(jù)庫如 Oracle, PostgreSQL, 及 MySQL 解決一些.NET Framework中不尋常的詭譎問題。
背景說明
從以往來看,一種很常見的數(shù)據(jù)庫設(shè)計(jì)是使用連續(xù)的整型作為標(biāo)識,該字段通常由服務(wù)器本身在插入新數(shù)據(jù)行時(shí)自動生成。這種簡介的方法適用與很多應(yīng)用程序。
然而在某些情況并不是很理想。隨著越來越多的使用Nhibernate和EntityFramework等對象關(guān)系映射(ORM)框架,依賴服務(wù)器自動生成主鍵的這種方式引發(fā)了許多 大多數(shù)人都希望避免的 并發(fā)問題。同樣,復(fù)制方案的時(shí)候這種依靠【單一認(rèn)證機(jī)構(gòu)源(single authoritative source)】生成的鍵值也會產(chǎn)生問題——因此,關(guān)鍵點(diǎn)是減少【單一權(quán)利機(jī)構(gòu)的作用(the role of a single authority)】。
一種很誘人的選擇是使用GUID作為唯一鍵值。GUID(全局唯一標(biāo)識符),也稱為UUID,長128-bit的值,能保證在所有時(shí)間和空間上獨(dú)一無二。RFC 41222 描述了創(chuàng)建標(biāo)準(zhǔn)GUID,如今大多數(shù)GUID生成算法通常是一個(gè)很長的隨機(jī)數(shù),再結(jié)合一些像網(wǎng)絡(luò)mac地址這種隨機(jī)本地組件信息。
GUID允許開發(fā)人員自行創(chuàng)建而不需要服務(wù)器去檢查是否已被他人使用導(dǎo)致沖突,咋一看,這是一種很好的解決方案。
那么問題是什么呢?性能。為了得到最好的新能,很多數(shù)據(jù)庫都使用聚焦索引,意味著表中的行的順序(通常基于主鍵)也是對應(yīng)存儲在磁盤中的的順序。這使得其能通過索引快速查找,但它也導(dǎo)致在插入 【主鍵不是在列表的末尾的(their primary key doesn't fall at the end of the list)】 新行時(shí)變得很慢。例如下面的數(shù)據(jù):
| ID | Name |
| 1 | Holmes, S. |
| 4 | Watson, J. |
| 7? | Moriarty, J. |
此時(shí)很簡單:數(shù)據(jù)行對應(yīng)ID列順序儲存。如果我們新添加一行ID為8,也不會產(chǎn)生問題,新行會附加的末尾。
| ID? | Name? |
| 1? | Holmes, S.? |
| 4? | Watson, J.? |
| 7? | Moriarty, J.? |
| 8? | Lestrade, I.? |
但如果我們想插入一行的ID為5:?
| ID? | Name? |
| 1?? | Holmes, S.? |
| 4? | Watson, J.? |
| 5 | Hudson, Mrs. |
| 7? | Moriarty, J.? |
| 8? | Lestrade, I.? |
?ID7,8行必須移動。雖然在這里不算什么事兒,但當(dāng)你的數(shù)據(jù)量達(dá)到數(shù)百萬行的級別之后,這就是個(gè)問題了。如果你還想要每秒處理上百次這種請求,那可真是難上加難了。
這就是GUID主鍵引發(fā)的問題:它可能是真正隨機(jī)生成的,至少表面看起來很像是隨機(jī)的,因?yàn)樗鼈兺ǔ2粫a(chǎn)生任何特定的順序。正因?yàn)槿绱?#xff0c;使用一個(gè) GUID 值作為任何規(guī)模的數(shù)據(jù)庫中的主鍵的一部分被認(rèn)為是非常糟糕的做法。?導(dǎo)致插入會很慢,還會涉及大量的不必要的磁盤活動。
有規(guī)則的GUID
?所以,GUID最關(guān)鍵的問題就是它缺乏規(guī)則。那么,就讓我們來制定一個(gè)規(guī)則。COMB 方法(這里聯(lián)合GUID/時(shí)間戳)使用一個(gè)保持增長(或至少不會減少)的值去替換GUID的某一組成部分。顧名思義,這是一個(gè)根據(jù)當(dāng)前日期時(shí)間生成的值。
舉個(gè)栗子,考慮下面這組GUID值:
fda437b5-6edd-42dc-9bbd-c09d10460ad0
2cb56c59-ef3d-4d24-90e7-835ed5968cdc
6bce82f3-5bd2-4efc-8832-986227592f26
42af7078-4b9c-4664-ba01-0d492ba3bd83?
請注意這組值沒有任何特定順序且本質(zhì)上是隨機(jī)生成的。插入100萬行以這種類型為主鍵的值就會非常慢。
現(xiàn)在考慮這種假定的特殊GUID值:???
00000001-a411-491d-969a-77bf40f55175
00000002-d97d-4bb9-a493-cad277999363
00000003-916c-4986-a363-0a9b9c95ca52
00000004-f827-452b-a3be-b77a3a4c95aa?
其中的第一部分已用遞增的序列替換——設(shè)想從項(xiàng)目啟動開始按毫秒計(jì)算。插入100萬條數(shù)據(jù)不是很糟糕,新行會依次追加到末尾不會對現(xiàn)有數(shù)據(jù)造成影響。
現(xiàn)在我們有了基本的概念,還需要進(jìn)一步了解 在各種不同的數(shù)據(jù)庫系統(tǒng)中 GUID是如何構(gòu)造的。
128-bit 的GUID主要有4部分組成,Data1, Data2, Data3, and Data4,你可以看成下面這樣:
11111111-2222-3333-4444-444444444444
Data1 占4個(gè)字節(jié), Data2 兩個(gè)字節(jié), Data3 兩個(gè)字節(jié)加?Data4 8個(gè)字節(jié)(其中Data3和Data4的第一部分或多或少保留有一些版本信息).??
現(xiàn)如今使用的有很多GUID算法,特別是在.NET 平臺, 像是被當(dāng)作了一個(gè)花哨的隨機(jī)數(shù)生成器 (微軟在過去曾將本機(jī)mac地址加入到生成GUID的運(yùn)算當(dāng)中, 但由于涉及到隱私問題這種方法在幾年前就停止使用了).??這對我們來說是件好事, 意味著,在它(GUID)值的某部分動點(diǎn)手腳不太可能會影響到它的唯一性.??
但不幸的是,不同的數(shù)據(jù)庫處理GUID的方式也是不同的。有些(Microsoft SQL Server, PostgreSQL) 具有內(nèi)置GUID類型可以儲存和直接操作GUID。沒有原生GUID支持的數(shù)據(jù)庫通過模擬而有不同的約定。例如MySQL, 通常將GUID儲存為char(36)的字符串表現(xiàn)形式. ?Oracle保存為raw bytes類型,一個(gè)GUID值為raw(16).
讀取的時(shí)候的處理則更加復(fù)雜, 因?yàn)镸icrosoft SQL Server一個(gè)比較另類之處是它按照 GUID末尾最不重要的那6個(gè)字節(jié)來排序(即. Data4中最后6個(gè)字節(jié))。所以,如果想在SQL Server中創(chuàng)建有序的GUID,我們不得不將有序部分放在最后面。大部分其他數(shù)據(jù)庫會把它放在開頭.
算法??
從不同數(shù)據(jù)庫GUID的處理方式來看,顯然沒有一個(gè)通用的有序GUID生成算法,我們針對不同應(yīng)用程序分別對待。經(jīng)過一番實(shí)驗(yàn)之后, 我確定了大部分為下面3中情況:
生成的GUID 按照字符串順序排列
生成的GUID 按照二進(jìn)制的順序排列
生成的GUID?像SQL Server, 按照末尾部分排列
(為什么字符串順序和二進(jìn)制順序有區(qū)別?因?yàn)樵?little-endian system 環(huán)境中.NET處理GUID和string的方式并不是你想象的和其他運(yùn)行.net的環(huán)境中的那樣。)????
?‘我’根據(jù)這些區(qū)別定義了如下的枚舉:
public enum SequentialGuidType {SequentialAsString,SequentialAsBinary,SequentialAtEnd }現(xiàn)在我們可以定義一個(gè)接受參數(shù)為上面枚舉類型的方法去生成我們的GUID:
public Guid NewSequentialGuid(SequentialGuidType guidType) {... }但是具體要怎么來創(chuàng)建我們想要的有序GUID呢?就是(GUID)的哪一部分我們要保留其“隨機(jī)性”、哪一部分要替換成時(shí)間戳?按照最初的 COMB 規(guī)范,針對 SQL Server,用時(shí)間戳的值替換Data4的最后6個(gè)字節(jié)。這是最簡單的因?yàn)?SQL Server 就是按照GUID那6個(gè)字節(jié)的值進(jìn)行排序的,通常情況這6個(gè)字節(jié)就已經(jīng)足夠了(這里應(yīng)該是值唯一性),再加上其他10字節(jié)的隨機(jī)值。
最重要的還是GUID的隨機(jī)性。就像我剛所說的,我們還需要構(gòu)建10字節(jié)的隨機(jī)值:
var rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); byte[] randomBytes = new byte[10]; rng.GetBytes(randomBytes);?我們使用了?RNGCryptoServiceProvider?來生成其中的隨機(jī)部分因?yàn)?System.Random?在這里有一些不足之處(它根據(jù)一些可識別的模式生成數(shù)字,例如它會在超過232?次迭代后循環(huán)而出現(xiàn)重復(fù))。我們依賴其隨機(jī)性就需要?RNGCryptoServiceProvider?來產(chǎn)生保密性強(qiáng)的隨機(jī)數(shù)據(jù)。?
(然而,這樣還是相對較慢,如果你想追求極致性能的話可以考慮另一種做法——例如直接使用?Guid.NewGuid()?來產(chǎn)生 byte[] 數(shù)據(jù)。‘我’通常不這樣做 因?yàn)?Guid.NewGuid()本身并不能保證其隨機(jī)性,還是使用據(jù)我所知比較可靠做法。)
Okay,隨機(jī)值部分我們已經(jīng)有了,剩下的就是用時(shí)間戳去替換排序的部分。我們使用6字節(jié)的時(shí)間戳,它根據(jù)什么得來呢?很明顯可以使用?DateTime.Now?(或者如 Rich Andersen 指出的使用?DateTime.UtcNow?有更好的性能)轉(zhuǎn)換為6字節(jié)的整型。它的Ticks屬性很誘人:能得到一個(gè)從January 1, 0001 A.D.至今的100毫微秒間隔數(shù)。但它仍然有一些問題。
首先,因?yàn)槠銽icks屬性返回一個(gè)64-bit的值但我們只需要48bit,我們必須丟掉2字節(jié),剩下的48bit有用的100毫微秒的時(shí)間間隔周期不到一年就會溢出。很多應(yīng)用程序的使用壽命會超過一年 這樣會毀了我們的最初愿望,我們需要使用時(shí)間不太精確的來測量。
另一個(gè)難題是?DateTime.UtcNow?的精確度太低。如這篇文章所描述的,它的值可能更新間隔為10毫秒。(可能在某些系統(tǒng)中會稍微頻繁點(diǎn),但我們依賴這個(gè)。)
好消息是,將這兩個(gè)結(jié)合起來問題可以相互抵消:由于長度限制不能使用整個(gè)Ticks值,所以我們拿來除以1000然后將末尾48bits作為時(shí)間戳。‘我’使用毫秒是因?yàn)?#xff0c;即使?DateTime.UtcNow?在某些系統(tǒng)中的準(zhǔn)確度只有10毫秒左右,將來會提高,期待。減少精確度之后我的時(shí)間戳溢出重復(fù)大約會在公元5800年之后,相信對大多數(shù)應(yīng)用程序來說這已經(jīng)足夠了。
在我們繼續(xù)之前提醒一下:使用精確度為1毫秒的時(shí)間戳意味著在GUID生成非常快的時(shí)候時(shí)間戳是有可能重復(fù)的,這時(shí)候就不會有順序。這在某些程序中可能很常見,事實(shí)上‘我’嘗試了一些替代方法,像使用高準(zhǔn)確度的?System.Diagnostics.Stopwatch?,或者將時(shí)間戳和一個(gè)‘計(jì)數(shù)器’結(jié)合來試圖保證在時(shí)間戳未更新是也能有順序。然而經(jīng)測試發(fā)現(xiàn)這樣做并沒有明顯差異,即使一次性生成10個(gè)甚至100個(gè)guid也可能會呈現(xiàn)出同樣的時(shí)間戳。這也符合 Jimmy Nilsson 在他的COMB中的測試結(jié)果。考慮到這一點(diǎn),就不再在這里糾結(jié)了.
代碼如下:
long timestamp = DateTime.UtcNow.Ticks / 10000L; byte[] timestampBytes = BitConverter.GetBytes(timestamp);現(xiàn)在時(shí)間戳我們有了,然而我們是從一個(gè)數(shù)字中通過?BitConverter?獲得字節(jié)的,還需要考慮字節(jié)順序:
if (BitConverter.IsLittleEndian) {Array.Reverse(timestampBytes); }我們有了生成GUID需要的隨機(jī)字節(jié)部分和時(shí)間戳字節(jié)部分。剩下的就是拼接它們了。在支持的數(shù)據(jù)庫類型?SequentialGuidType?數(shù)量這一點(diǎn)上,我們得量力而為。?SequentialAsBinary?和?SequentialAsString的時(shí)候時(shí)間戳排在前面,?SequentialAtEnd?相反。
byte[] guidBytes = new byte[16];switch (guidType) {case SequentialGuidType.SequentialAsString:case SequentialGuidType.SequentialAsBinary:Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);break;case SequentialGuidType.SequentialAtEnd:Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);break; }到目前為止一切順利;但現(xiàn)在我們走進(jìn)了.net framework的一個(gè)怪癖區(qū)域,它不僅是將GUID當(dāng)成一個(gè)byte序列組,出于某種原因,它還認(rèn)為GUID是一個(gè)包含一個(gè)Int32、兩個(gè)Int16及8byte的struct。換句話說,它認(rèn)為Data1是Int32、Data2和Data3是Int16、Data4是Byte[8]。
這對我們來說意味著什么?..主要問題有字節(jié)得重新排序,由于.net認(rèn)為它處理的是數(shù)字,我們得補(bǔ)償little-endian system ——但是!——時(shí)間戳在前面的時(shí)候,只為應(yīng)用程序?qū)UID值轉(zhuǎn)換為一個(gè)字符串(時(shí)間戳在末尾的不重要[因?yàn)镈ata4不會被當(dāng)作數(shù)字]不需要作任何處理)
這就是上面提到的將GUID作為字符串和二進(jìn)制有區(qū)別的原因。[將GUID]作為字符串存儲的數(shù)據(jù)庫,ORM框架或應(yīng)用程序會使用??ToString()?來生成 SQL INSERT 語句,意思是我們需要改進(jìn)字節(jié)存儲順序問題。[將GUID]當(dāng)作二進(jìn)制數(shù)據(jù)存儲的數(shù)據(jù)庫,可能會使用?Guid.ToByteArray()?插入,我們不需要修正。因此,我們最后再加上:
if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian) {Array.Reverse(guidBytes, 0, 4);Array.Reverse(guidBytes, 4, 2); }現(xiàn)在使用參數(shù)為byte[]的構(gòu)造函數(shù)返回GUID:
return new Guid(guidBytes);使用
要使用我們的方法,首先得確定我們使用的是那種數(shù)據(jù)庫及ORM框架,為了方便,下面是一些常見的數(shù)據(jù)庫類型,可能與你應(yīng)用程序的實(shí)際情況有出入:
| Database? | GUID Column? | SequentialGuidType Value? |
| Microsoft SQL Server? | uniqueidentifier? | SequentialAtEnd |
| MySQL? | char(36)? | SequentialAsString? |
| Oracle? | raw(16)? | SequentialAsBinary? |
| PostgreSQL? | uuid? | SequentialAsString? |
| SQLite?? | varies?? | varies? |
(SQLite沒有GUID類型字段,但有類似功能的擴(kuò)展,然而根據(jù)?BinaryGUID?傳參的不同,內(nèi)部可能存儲為16字節(jié)長度的二進(jìn)制數(shù)據(jù)或長度36的text,所以這里沒有一個(gè)統(tǒng)一方案)。
這里有一些該方法生成的樣本數(shù)據(jù)。
第一個(gè)?NewSequentialGuid(SequentialGuidType.SequentialAsString)?:
39babcb4-e446-4ed5-4012-2e27653a9d13
39babcb4-e447-ae68-4a32-19eb8d91765d
39babcb4-e44a-6c41-0fb4-21edd4697f43
39babcb4-e44d-51d2-c4b0-7d8489691c70
如上,其中前6個(gè)字節(jié)(前2部分)是排序部分,剩下的是隨機(jī)值。將這樣的值插入將GUID當(dāng)作字符串的數(shù)據(jù)庫(如MySQL)時(shí)相對與無序GUID性能會有顯著提升。
接下來是?NewSequentialGuid(SequentialGuidType.SequentialAtEnd)?:
a47ec5e3-8d62-4cc1-e132-39babcb4e47a
939aa853-5dc9-4542-0064-39babcb4e47c
7c06fdf6-dca2-4a1a-c3d7-39babcb4e47d
c21a4d6f-407e-48cf-656c-39babcb4e480
排序的末尾的6個(gè)字節(jié),其余是隨機(jī)部分。‘我’不明白為什么SQL Server的?uniqueidentifier?是這個(gè)樣子,但這樣做同樣也沒什么問題。
最后是?NewSequentialGuid(SequentialGuidType.SequentialAsBinary)?:
b4bcba39-58eb-47ce-8890-71e7867d67a5
b4bcba39-5aeb-42a0-0b11-db83dd3c635b
b4bcba39-6aeb-4129-a9a5-a500aac0c5cd
b4bcba39-6ceb-494d-a978-c29cef95d37f
當(dāng)我們?ToString()?之后再看是發(fā)現(xiàn)某些東西出錯(cuò)了:前面的兩部分‘調(diào)換’了(因?yàn)榍懊嫣岬阶址M(jìn)制問題),如果插入的是字符串字段(MySQL),性能不會有任何提升。
問題產(chǎn)生的原因是使用了?ToString()方法。上面4組值如果是使用?Guid.ToByteArray()?轉(zhuǎn)換為16字符串的話:
39babcb4eb5847ce889071e7867d67a5
39babcb4eb5a42a00b11db83dd3c635b
39babcb4eb6a4129a9a5a500aac0c5cd
39babcb4eb6c494da978c29cef95d37f
這是大多數(shù)ORM框架針對Oracle數(shù)據(jù)庫所作的處理,你會發(fā)現(xiàn),這樣做之后,排序功能又有作用了。
回顧一下,我們現(xiàn)在有一個(gè)方法可以針對3中不同的數(shù)據(jù)庫(將GUID儲存為字符串MySQL, 可能有SQLite;作為二進(jìn)制數(shù)據(jù)的Oracle, PostgreSQL;以及有另類儲存方案的Microsoft SQL Server)生成有序的GUID值。
后面我們還可以擴(kuò)展我們的方法,自動檢測數(shù)據(jù)庫類型,或者通過?DbConnection?判斷,但可能會取決于我們應(yīng)用程序所使用的ORM框架,留給你們自由發(fā)揮。
對比數(shù)據(jù)【如今各數(shù)據(jù)庫版本變遷,實(shí)際數(shù)據(jù)效果可能相差很大,個(gè)人對這些性能測試不感興趣】
選了4個(gè)常用的數(shù)據(jù)庫(Microsoft SQL Server 2008, MySQL 5.5, Oracle XE 11.2, and PostgreSQL 9.1)在windows7桌面系統(tǒng)中進(jìn)行測試。
測試是通過每個(gè)數(shù)據(jù)的命令插入 GUID主鍵和100字符長度的text【譯者注:text類型已經(jīng)被nvarchar(max)代替】的 2百萬行數(shù)據(jù),首先是測試我們方法的3中不同算法,接著是?Guid.NewGuid()?最后比較int類型,圖中還對比了前100w行及后面100w行分別使用的時(shí)長(毫秒):
在SQL Server中,SequentialAtEnd方式效果最好,性能很接近int,對比NewGuid()無序生成性能提升了接近75%;后面100w行稍微慢點(diǎn)【which is consistent with a lot of page-shuffling to maintain the clustered index as more rows get added in the middle】,其他兩種方式與無序方式相差不大,說明SequentialAtEnd的方式工作良好。
如圖,無序GUID在MySql中的性能非常低,慢到我不得不砍掉圖中后100w行的具體性能顯示(后100w行插入比前面慢了一半);使用SequentialAsString的算法是性能接近與int,說明達(dá)到了排序的目的。
?
Oracle中就不那么明顯了,使用raw(16)來儲存GUID。我們使用?SequentialAsBinary?時(shí)最快,但即使使用無序GUID也并沒有慢很多;此外有序GUID的插入還比int稍快,至少這的測試結(jié)果是這樣的,我不得不懷疑Oracle是否也有某種怪癖。,,
最后是 PostgreSQL,和Oracle一樣也沒有明顯的提升,只是最快的是使用SequentialAsString,只比int慢了7.8左右,但相比無序GUID節(jié)省了近一半的時(shí)間。
其他
這里有幾點(diǎn)需要考慮。這里重點(diǎn)關(guān)注了插入有序guid的性能;但是對比?Guid.NewGuid()?,創(chuàng)建GUID的性能消耗該怎么算呢?好吧,它確實(shí)很慢:在‘我’的系統(tǒng),生成100w無序GUID需要140毫秒,但生成有序GUID需要2800毫秒——慢了20倍。
一些快速測試表明慢的主要原因是使用了?RNGCryptoServiceProvider?來生成隨機(jī)數(shù)據(jù);使用?System.Random?能降到400毫秒左右。但‘我’還是不推薦這樣做?System.Random?在這里仍然有問題。當(dāng)然可能會有其他比這更好的算法——誠然‘我’不是很懂隨機(jī)數(shù)生成。
這點(diǎn)很值得注意嗎?個(gè)人覺得在可接受范圍。除非你的應(yīng)用涉及非常頻繁的插入(這種情況GUID主鍵不是很理想),相比有序GUID帶來的好處,這點(diǎn)問題微不足道。
另一個(gè)問題是:6字節(jié)的時(shí)間戳占用意味著僅有10個(gè)字節(jié)的隨機(jī)數(shù)據(jù),這可能會危機(jī)唯一性。包括時(shí)間戳,是保證創(chuàng)建 間隔相隔幾毫秒 的兩個(gè)GUID唯一性——這是一個(gè)約定,即使是完全隨機(jī)的GUID(如Guid.NewGuid()創(chuàng)建的),如果兩個(gè)GUID創(chuàng)建時(shí)間太過接近會怎么樣呢?10-byte的強(qiáng)加密型隨機(jī)數(shù)意味著有280,1,208,925,819,614,629,174,706,176 種可能的組合。這樣一來,生成重復(fù)GUID的概率微不足道。
最后一點(diǎn),這種方式生成的GUID的格式并不滿足 RFC 4122 規(guī)范——它們?nèi)鄙侔姹咎?#xff08;通常在bit48-51位),‘我’不認(rèn)為這很必要;‘我’不知道是否有數(shù)據(jù)庫去真正在意這一結(jié)構(gòu),省略它多出了4位作為隨機(jī)部分。當(dāng)然要加上這個(gè)也很容易。
完整代碼
這是這個(gè)方法的完整實(shí)現(xiàn)代碼。有一些小的修改(如抽象靜態(tài)隨機(jī)生成器實(shí)例以及重構(gòu)switch塊):
using System; using System.Security.Cryptography;public enum SequentialGuidType {SequentialAsString,SequentialAsBinary,SequentialAtEnd }public static class SequentialGuidGenerator {private static readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();public static Guid NewSequentialGuid(SequentialGuidType guidType){byte[] randomBytes = new byte[10];_rng.GetBytes(randomBytes);long timestamp = DateTime.UtcNow.Ticks / 10000L;byte[] timestampBytes = BitConverter.GetBytes(timestamp);if (BitConverter.IsLittleEndian){Array.Reverse(timestampBytes);}byte[] guidBytes = new byte[16];switch (guidType){case SequentialGuidType.SequentialAsString:case SequentialGuidType.SequentialAsBinary:Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);// If formatting as a string, we have to reverse the order// of the Data1 and Data2 blocks on little-endian systems.if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian){Array.Reverse(guidBytes, 0, 4);Array.Reverse(guidBytes, 4, 2);}break;case SequentialGuidType.SequentialAtEnd:Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);break;}return new Guid(guidBytes);} }最終代碼和演示項(xiàng)目?https://github.com/jhtodd/SequentialGuid[^]
結(jié)論
如開頭所說的,這種通用實(shí)現(xiàn)已經(jīng)集成到各種重量級框架中;這里已經(jīng)不是最新的,‘我’的目的是說明實(shí)現(xiàn)的方法及原理,根據(jù)不同的數(shù)據(jù)庫環(huán)境加以調(diào)整以適應(yīng)具體需求。
一點(diǎn)點(diǎn)的不斷努力嘗試,就有可能實(shí)現(xiàn)適用于任何數(shù)據(jù)庫的有序GUID生成方法。
總結(jié)
以上是生活随笔為你收集整理的使用有序GUID:提升其在各数据库中作为主键时的性能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core开发实战(第17课:为
- 下一篇: 【朝夕技术专刊】Core3.1WebAp