為了得到最大的性能,,一般數(shù)據(jù)庫都有并發(fā)機(jī)制,不過帶來的問題就是數(shù)據(jù)訪問的沖突,。為了解決這個(gè)問題,,大多數(shù)數(shù)據(jù)庫用的方法就是數(shù)據(jù)的鎖定。

數(shù)據(jù)的鎖定分為兩種方法,第一種叫做悲觀鎖,,第二種叫做樂觀鎖,。什么叫悲觀鎖呢,悲觀鎖顧名思義,,就是對(duì)數(shù)據(jù)的沖突采取一種悲觀的態(tài)度,,也就是說假設(shè)數(shù)據(jù)肯定會(huì)沖突,所以在數(shù)據(jù)開始讀取的時(shí)候就把數(shù)據(jù)鎖定住,。而樂觀鎖就是認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),,如果發(fā)現(xiàn)沖突了,,則讓用戶返回錯(cuò)誤的信息,讓用戶決定如何去做,。

先從悲觀鎖開始說,。在SqlServer等其余很多數(shù)據(jù)庫中,數(shù)據(jù)的鎖定通常采用頁級(jí)鎖的方式,,也就是說對(duì)一張表內(nèi)的數(shù)據(jù)是一種串行化的更新插入機(jī)制,,在任何時(shí)間同一張表只會(huì)插1條數(shù)據(jù),別的想插入的數(shù)據(jù)要等到這一條數(shù)據(jù)插完以后才能依次插入,。帶來的后果就是性能的降低,,在多用戶并發(fā)訪問的時(shí)候,當(dāng)對(duì)一張表進(jìn)行頻繁操作時(shí),,會(huì)發(fā)現(xiàn)響應(yīng)效率很低,,數(shù)據(jù)庫經(jīng)常處于一種假死狀態(tài)。而Oracle用的是行級(jí)鎖,,只是對(duì)想鎖定的數(shù)據(jù)才進(jìn)行鎖定,,其余的數(shù)據(jù)不相干,所以在對(duì)Oracle表中并發(fā)插數(shù)據(jù)的時(shí)候,,基本上不會(huì)有任何影響,。

注:對(duì)于悲觀鎖是針對(duì)并發(fā)的可能性比較大,而一般在我們的應(yīng)用中用樂觀鎖足以,。


Oracle的悲觀鎖需要利用一條現(xiàn)有的連接,,分成兩種方式,從SQL語句的區(qū)別來看,,就是一種是for update,一種是for update nowait的形式,。比如我們看一個(gè)例子,。首先建立測(cè)試用的數(shù)據(jù)庫表。

CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept


這里我們利用了Oracle的Sample的scott用戶的表,把數(shù)據(jù)copy到我們的test表中,。首先我們看一下for update鎖定方式,。首先我們執(zhí)行如下的select for update語句。

select * from test where id = 10 for update

通過這條檢索語句鎖定以后,,再開另外一個(gè)sql*plus窗口進(jìn)行操作,,再把上面這條sql語句執(zhí)行一便,你會(huì)發(fā)現(xiàn)sqlplus好像死在那里了,,好像檢索不到數(shù)據(jù)的樣子,,但是也不返回任何結(jié)果,就屬于卡在那里的感覺,。這個(gè)時(shí)候是什么原因呢,,就是一開始的第一個(gè)Session中的select for update語句把數(shù)據(jù)鎖定住了。由于這里鎖定的機(jī)制是wait的狀態(tài)(只要不表示nowait那就是wait),,所以第二個(gè)Session(也就是卡住的那個(gè)sql*plus)中當(dāng)前這個(gè)檢索就處于等待狀態(tài),。當(dāng)?shù)谝粋€(gè)session最后commit或者rollback之后,第二個(gè)session中的檢索結(jié)果就是自動(dòng)跳出來,,并且也把數(shù)據(jù)鎖定住,。不過如果你第二個(gè)session中你的檢索語句如下所示。

select * from test where id = 10


也就是沒有for update這種鎖定數(shù)據(jù)的語句的話,,就不會(huì)造成阻塞了,。另外一種情況,就是當(dāng)數(shù)據(jù)庫數(shù)據(jù)被鎖定的時(shí)候,,也就是執(zhí)行剛才for update那條sql以后,,我們?cè)诹硗庖粋€(gè)session中執(zhí)行for update nowait后又是什么樣呢。比如如下的sql語句,。 由于這條語句中是制定采用nowait方式來進(jìn)行檢索,,所以當(dāng)發(fā)現(xiàn)數(shù)據(jù)被別的session鎖定中的時(shí)候,就會(huì)迅速返回ORA-00054錯(cuò)誤,,內(nèi)容是資源正忙, 但指定以 NOWAIT 方式獲取資源,。所以在程序中我們可以采用nowait方式迅速判斷當(dāng)前數(shù)據(jù)是否被鎖定中,如果鎖定中的話,,就要采取相應(yīng)的業(yè)務(wù)措施進(jìn)行處理,。

select * from test where id = 10 for update nowait


那這里另外一個(gè)問題,就是當(dāng)我們鎖定住數(shù)據(jù)的時(shí)候,,我們對(duì)數(shù)據(jù)進(jìn)行更新和刪除的話會(huì)是什么樣呢,。比如同樣,我們讓第一個(gè)Session鎖定住id=10的那條數(shù)據(jù),,我們?cè)诘诙€(gè)session中執(zhí)行如下語句,。

update test set value=2 where id = 10


這個(gè)時(shí)候我們發(fā)現(xiàn)update語句就好像select for update語句一樣也停住卡在這里,,當(dāng)你第一個(gè)session放開鎖定以后update才能正常運(yùn)行。當(dāng)你update運(yùn)行后,,數(shù)據(jù)又被你update語句鎖定住了,,這個(gè)時(shí)候只要你update后還沒有commit,別的session照樣不能對(duì)數(shù)據(jù)進(jìn)行鎖定更新等等,。

總之,,Oracle中的悲觀鎖就是利用Oracle的Connection對(duì)數(shù)據(jù)進(jìn)行鎖定。在Oracle中,,用這種行級(jí)鎖帶來的性能損失是很小的,,只是要注意程序邏輯,不要給你一不小心搞成死鎖了就好,。而且由于數(shù)據(jù)的及時(shí)鎖定,,在數(shù)據(jù)提交時(shí)候就不呼出現(xiàn)沖突,可以省去很多惱人的數(shù)據(jù)沖突處理,。缺點(diǎn)就是你必須要始終有一條數(shù)據(jù)庫連接,,就是說在整個(gè)鎖定到最后放開鎖的過程中,你的數(shù)據(jù)庫聯(lián)接要始終保持住,。與悲觀鎖相對(duì)的,,我們有了樂觀鎖。樂觀鎖一開始也說了,,就是一開始假設(shè)不會(huì)造成數(shù)據(jù)沖突,,在最后提交的時(shí)候再進(jìn)行數(shù)據(jù)沖突檢測(cè)。在樂觀鎖中,,我們有3種

常用的做法來實(shí)現(xiàn),。

[1]第一種就是在數(shù)據(jù)取得的時(shí)候把整個(gè)數(shù)據(jù)都copy到應(yīng)用中,在進(jìn)行提交的時(shí)候比對(duì)當(dāng)前數(shù)據(jù)庫中的數(shù)據(jù)和開始的時(shí)候更新前取得的數(shù)據(jù),。當(dāng)發(fā)現(xiàn)兩個(gè)數(shù)據(jù)一模一樣以后,,就表示沒有沖突可以提交,否則則是并發(fā)沖突,,需要去用業(yè)務(wù)邏輯進(jìn)行解決,。

[2]第二種樂觀鎖的做法就是采用版本戳,這個(gè)在Hibernate中得到了使用,。采用版本戳的話,,首先需要在你有樂觀鎖的數(shù)據(jù)庫table上建立一個(gè)新的column,比如為number型,,當(dāng)你數(shù)據(jù)每更新一次的時(shí)候,,版本數(shù)就會(huì)往上增加1。比如同樣有2個(gè)session同樣對(duì)某條數(shù)據(jù)進(jìn)行操作,。兩者都取到當(dāng)前的數(shù)據(jù)的版本號(hào)為1,,當(dāng)?shù)谝粋€(gè)session進(jìn)行數(shù)據(jù)更新后,,在提交的時(shí)候查看到當(dāng)前數(shù)據(jù)的版本還為1,和自己一開始取到的版本相同,。就正式提交,然后把版本號(hào)增加1,,這個(gè)時(shí)候當(dāng)前數(shù)據(jù)的版本為2,。當(dāng)?shù)诙€(gè)session也更新了數(shù)據(jù)提交的時(shí)候,發(fā)現(xiàn)數(shù)據(jù)庫中版本為2,,和一開始這個(gè)session取到的版本號(hào)不一致,,就知道別人更新過此條數(shù)據(jù),這個(gè)

時(shí)候再進(jìn)行業(yè)務(wù)處理,,比如整個(gè)Transaction都Rollback等等操作,。在用版本戳的時(shí)候,可以在應(yīng)用程序側(cè)使用版本戳的驗(yàn)證,,也可以在數(shù)據(jù)庫側(cè)采用Trigger(觸發(fā)器)來進(jìn)行驗(yàn)證,。不過數(shù)據(jù)庫的Trigger的性能開銷還是比較的大,所以能在應(yīng)用側(cè)進(jìn)行驗(yàn)證的話還是推薦不用Trigger,。

[3]第三種做法和第二種做法有點(diǎn)類似,,就是也新增一個(gè)Table的Column,不過這次這個(gè)column是采用timestamp型,,存儲(chǔ)數(shù)據(jù)最后更新的時(shí)間,。在Oracle9i以后可以采用新的數(shù)據(jù)類型,也就是timestamp with time zone類型來做時(shí)間戳,。這種Timestamp的數(shù)據(jù)精度在Oracle的時(shí)間類型中是最高的,,精確到微秒(還沒與到納秒的級(jí)別),一般來說,,加上數(shù)據(jù)庫處理時(shí)間和人的思考動(dòng)作時(shí)間,,微秒級(jí)別是非常非常夠了,其實(shí)只要精確到毫秒甚至秒都應(yīng)該沒有什么問題,。和剛才的版本戳類似,,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫中數(shù)據(jù)的時(shí)間戳和自己更新前取到的時(shí)間戳進(jìn)行對(duì)比,如果一致則OK,,否則就是版本沖突,。如果不想把代碼寫在程序中或者由于別的原因無法把代碼寫在現(xiàn)有的程序中,也可以把這個(gè)時(shí)間戳樂觀鎖邏輯寫在Trigger或者存儲(chǔ)過程中,。