為了得到最大的性能,一般數(shù)據(jù)庫都有并發(fā)機(jī)制,不過帶來的問題就是數(shù)據(jù)訪問的沖突。為了解決這個(gè)問題,大多數(shù)數(shù)據(jù)庫用的方法就是數(shù)據(jù)的鎖定。
為了得到最大的性能,一般數(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ì)有任何影響。
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)行鎖定更新等等。