為了得到最大的性能,一般數(shù)據(jù)庫(kù)都有并發(fā)機(jī)制,不過(guò)帶來(lái)的問(wèn)題就是數(shù)據(jù)訪(fǎng)問(wèn)的沖突。為了解決這個(gè)問(wèn)題,大多數(shù)數(shù)據(jù)庫(kù)用的方法就是數(shù)據(jù)的鎖定。
為了得到最大的性能,一般數(shù)據(jù)庫(kù)都有并發(fā)機(jī)制,不過(guò)帶來(lái)的問(wèn)題就是數(shù)據(jù)訪(fǎng)問(wèn)的沖突。為了解決這個(gè)問(wèn)題,大多數(shù)數(shù)據(jù)庫(kù)用的方法就是數(shù)據(jù)的鎖定。
數(shù)據(jù)的鎖定分為兩種方法,第一種叫做悲觀鎖,第二種叫做樂(lè)觀鎖。什么叫悲觀鎖呢,悲觀鎖顧名思義,就是對(duì)數(shù)據(jù)的沖突采取一種悲觀的態(tài)度,也就是說(shuō)假設(shè)數(shù)據(jù)肯定會(huì)沖突,所以在數(shù)據(jù)開(kāi)始讀取的時(shí)候就把數(shù)據(jù)鎖定住。而樂(lè)觀鎖就是認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓用戶(hù)返回錯(cuò)誤的信息,讓用戶(hù)決定如何去做。
先從悲觀鎖開(kāi)始說(shuō)。在SqlServer等其余很多數(shù)據(jù)庫(kù)中,數(shù)據(jù)的鎖定通常采用頁(yè)級(jí)鎖的方式,也就是說(shuō)對(duì)一張表內(nèi)的數(shù)據(jù)是一種串行化的更新插入機(jī)制,在任何時(shí)間同一張表只會(huì)插1條數(shù)據(jù),別的想插入的數(shù)據(jù)要等到這一條數(shù)據(jù)插完以后才能依次插入。帶來(lái)的后果就是性能的降低,在多用戶(hù)并發(fā)訪(fǎng)問(wèn)的時(shí)候,當(dāng)對(duì)一張表進(jìn)行頻繁操作時(shí),會(huì)發(fā)現(xiàn)響應(yīng)效率很低,數(shù)據(jù)庫(kù)經(jīng)常處于一種假死狀態(tài)。而Oracle用的是行級(jí)鎖,只是對(duì)想鎖定的數(shù)據(jù)才進(jìn)行鎖定,其余的數(shù)據(jù)不相干,所以在對(duì)Oracle表中并發(fā)插數(shù)據(jù)的時(shí)候,基本上不會(huì)有任何影響。
Oracle的悲觀鎖需要利用一條現(xiàn)有的連接,分成兩種方式,從SQL語(yǔ)句的區(qū)別來(lái)看,就是一種是for update,一種是for update nowait的形式。比如我們看一個(gè)例子。首先建立測(cè)試用的數(shù)據(jù)庫(kù)表。
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用戶(hù)的表,把數(shù)據(jù)copy到我們的test表中。首先我們看一下for update鎖定方式。首先我們執(zhí)行如下的select for update語(yǔ)句。
select * from test where id = 10 for update |
通過(guò)這條檢索語(yǔ)句鎖定以后,再開(kāi)另外一個(gè)sql*plus窗口進(jìn)行操作,再把上面這條sql語(yǔ)句執(zhí)行一便,你會(huì)發(fā)現(xiàn)sqlplus好像死在那里了,好像檢索不到數(shù)據(jù)的樣子,但是也不返回任何結(jié)果,就屬于卡在那里的感覺(jué)。這個(gè)時(shí)候是什么原因呢,就是一開(kāi)始的第一個(gè)Session中的select for update語(yǔ)句把數(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)跳出來(lái),并且也把數(shù)據(jù)鎖定住。不過(guò)如果你第二個(gè)session中你的檢索語(yǔ)句如下所示。
select * from test where id = 10 |
也就是沒(méi)有for update這種鎖定數(shù)據(jù)的語(yǔ)句的話(huà),就不會(huì)造成阻塞了。另外一種情況,就是當(dāng)數(shù)據(jù)庫(kù)數(shù)據(jù)被鎖定的時(shí)候,也就是執(zhí)行剛才for update那條sql以后,我們?cè)诹硗庖粋€(gè)session中執(zhí)行for update nowait后又是什么樣呢。比如如下的sql語(yǔ)句。 由于這條語(yǔ)句中是制定采用nowait方式來(lái)進(jìn)行檢索,所以當(dāng)發(fā)現(xiàn)數(shù)據(jù)被別的session鎖定中的時(shí)候,就會(huì)迅速返回ORA-00054錯(cuò)誤,內(nèi)容是資源正忙, 但指定以 NOWAIT 方式獲取資源。所以在程序中我們可以采用nowait方式迅速判斷當(dāng)前數(shù)據(jù)是否被鎖定中,如果鎖定中的話(huà),就要采取相應(yīng)的業(yè)務(wù)措施進(jìn)行處理。
select * from test where id = 10 for update nowait |
那這里另外一個(gè)問(wèn)題,就是當(dāng)我們鎖定住數(shù)據(jù)的時(shí)候,我們對(duì)數(shù)據(jù)進(jìn)行更新和刪除的話(huà)會(huì)是什么樣呢。比如同樣,我們讓第一個(gè)Session鎖定住id=10的那條數(shù)據(jù),我們?cè)诘诙€(gè)session中執(zhí)行如下語(yǔ)句
update test set value=2 where id = 10 |
這個(gè)時(shí)候我們發(fā)現(xiàn)update語(yǔ)句就好像select for update語(yǔ)句一樣也停住卡在這里,當(dāng)你第一個(gè)session放開(kāi)鎖定以后update才能正常運(yùn)行。當(dāng)你update運(yùn)行后,數(shù)據(jù)又被你update語(yǔ)句鎖定住了,這個(gè)時(shí)候只要你update后還沒(méi)有commit,別的session照樣不能對(duì)數(shù)據(jù)進(jìn)行鎖定更新等等。