原創(chuàng)|其它|編輯:郝浩|2009-11-23 11:02:52.000|閱讀 1067 次
概述:前幾天一個(gè)人問(wèn)到了關(guān)于流水號(hào)重復(fù)的問(wèn)題,我想了下,雖然說(shuō)這個(gè)問(wèn)題比較簡(jiǎn)單,但是具有廣泛性,所以寫(xiě)了這篇博客來(lái)介紹下,希望對(duì)大家有所幫助。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
前幾天一個(gè)人問(wèn)到了關(guān)于流水號(hào)重復(fù)的問(wèn)題,我想了下,雖然說(shuō)這個(gè)問(wèn)題比較簡(jiǎn)單,但是具有廣泛性,所以寫(xiě)了這篇博客來(lái)介紹下,希望對(duì)大家有所幫助。
在進(jìn)行數(shù)據(jù)庫(kù)應(yīng)用開(kāi)發(fā)時(shí)經(jīng)常會(huì)遇到生成流水號(hào)的情況,比如說(shuō)做了一個(gè)訂單模塊,要求訂單號(hào)是唯一的,規(guī)則是:下訂單時(shí)的年月日+6位的流水號(hào)這樣的規(guī)則。
對(duì)于這種要生成流水號(hào)的系統(tǒng),我們一般是在數(shù)據(jù)庫(kù)中新建了一個(gè)種子表,每次生成新的訂單時(shí):
1.讀取當(dāng)天種子最大值。
2.根據(jù)種子最大值和當(dāng)時(shí)的年月日生成唯一的訂單號(hào)。
3.更新種子最大值,使最大值+1。
4.根據(jù)生成的訂單號(hào)將訂單數(shù)據(jù)插入到訂單表中。
以上幾步操作是在一個(gè)事務(wù)中完成,保證了流水號(hào)的連續(xù)。這個(gè)思路是正確的,使用起來(lái)好像也沒(méi)有什么問(wèn)題,但是在業(yè)務(wù)量比較大的情況下卻經(jīng)常報(bào)錯(cuò):“訂單號(hào)違反主鍵約束,不能將重復(fù)的訂單號(hào)插入到訂單表中。”這是怎么回事?讓我們做一個(gè)簡(jiǎn)單的Demo來(lái)重現(xiàn)一下:
1.創(chuàng)建種子表和訂單表,這里只是一個(gè)簡(jiǎn)單的Demo,所以就省去了很多字段,而且訂單號(hào)假設(shè)就是一個(gè)流水號(hào),不用再使用年月日+6位流水號(hào)了。
CREATETABLESeek--種子表
(
SeekValueINT
)
GO
INSERTINTOSeekVALUES(0)--種子初始值為0
GO
CREATETABLEOrders
(
OrderIDINTPRIMARYKEY,--訂單號(hào),主鍵
RemarkVARCHAR(5)NOTNULL
)
2.創(chuàng)建一個(gè)存儲(chǔ)過(guò)程,該存儲(chǔ)過(guò)程傳入Remark參數(shù),根據(jù)生成的流水號(hào)插入到訂單表中:
CREATEPROCAddOrder--Author:深藍(lán)
@remarkVARCHAR(5)--傳入的參數(shù)
AS
DECLARE@seekint
BEGINTRAN --開(kāi)啟一個(gè)事務(wù)
SELECT@seek=SeekValue--讀取種子表中的最大值作為流水號(hào)
FROMSeek
--生成訂單號(hào)這一步省略,因?yàn)檫@里假定的訂單的編號(hào)就是流水號(hào)
UPDATESeekSETSeekValue=@seek+1--更新種子表,使最大值+1
INSERTINTOt1VALUES(@seek,@remark)--插入一條訂單數(shù)據(jù)
COMMIT--提交事務(wù)
3.新建一個(gè)查詢窗口,使用以下語(yǔ)句調(diào)用創(chuàng)建的存儲(chǔ)過(guò)程,不斷的插入新訂單:
WHILE1=1
EXECAddOrder'test1'--不斷的插入訂單
4.再新建一個(gè)查詢窗口,使用通過(guò)的方式,不斷的插入新訂單,這樣用于模擬高并發(fā)時(shí)候的情況:
WHILE1=1
EXECAddOrder'test2'
5.運(yùn)行了一段時(shí)間后,我們停止這兩個(gè)死循環(huán),我們可以看到消息窗口中存在大量的異常:
消息 2627,級(jí)別 14,狀態(tài) 1,過(guò)程 AddOrder,第 11 行
違反了 PRIMARY KEY 約束 'PK__Orders__C3905BAF08EA5793'。不能在對(duì)象 'dbo.Orders' 中插入重復(fù)鍵。
語(yǔ)句已終止。
為什么會(huì)這樣呢?這得從事務(wù)隔離級(jí)別和鎖來(lái)解釋:
一般我們寫(xiě)程序時(shí)都是使用的是默認(rèn)的事務(wù)隔離級(jí)別——已提交讀,在第一步查詢Seek表時(shí),系統(tǒng)會(huì)為該表放置共享鎖,而鎖的兼容性中共享鎖和共享鎖是可以兼容的,所以一個(gè)事務(wù)在讀取Seek表最大值時(shí),其他事務(wù)也可以讀取出相同的最大值,兩個(gè)事務(wù)中讀取到了相同的最大值,所以產(chǎn)生了相同的流水號(hào),所以產(chǎn)生了相同的訂單號(hào),所以才會(huì)出現(xiàn)違反主鍵約束的錯(cuò)誤。
既然知道了這其中的原理了,那么解決辦法也就有了,只需要先對(duì)種子表中的數(shù)+1,然后再進(jìn)行讀取即可,修改存儲(chǔ)過(guò)程如下:
ALTERPROCAddOrder--Author:深藍(lán)
@remarkVARCHAR(5)
AS
DECLARE@seekint
BEGINTRAN
UPDATESeekSETSeekValue=SeekValue+1 --先修改數(shù)據(jù)
SELECT@seek=SeekValue-1--已經(jīng)加了1,所以這里-1下來(lái)
FROMSeek
INSERTINTOOrdersVALUES(@seek,@remark)
COMMIT
為什么這樣寫(xiě)就可以呢?第一步執(zhí)行更新操作,系統(tǒng)會(huì)請(qǐng)求更新鎖然后再升級(jí)為排他鎖,因?yàn)楦骆i和更新鎖以及排他鎖都是不兼容的,所以一個(gè)事務(wù)對(duì)Seek表進(jìn)行了更新后,其他的事務(wù)就不能對(duì)表進(jìn)行更新操作,只有等到事務(wù)提交以后才能繼續(xù)。
這里附上鎖兼容性表:
現(xiàn)有授予模式
請(qǐng)求模式ISSUIXSIXX
意向共享 (IS)是是是是是否
共享 (S)是是是否否否
更新 (U)是是否否否否
意向排他 (IX)是否否是否否
意向排他共享 (SIX)是否否否否否
排他 (X)否否否否否否
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載