先來看看這段代碼:
while s in (select * from table1) loop
insert into table1 values(s.field1,s.field2,s.field3,s.field4,s.field5);
end loop;
如果是SQL server的開發人員,看到這段代碼,肯定會搖頭:這段代碼有問題,這樣遞迴插入,會一直到表爆掉為止;而ORACLE開發人員,則不會有覺得這段代碼有什麼不對。
這是為什麼呢。原因就是兩個資料庫系統的處理機制不一樣,假設表table1的內容如下:
field1 field2 field3 field4 field5
1 2 3 4 5
那麼SQL server會這樣處理:找到table1的第一條記錄,把這條記錄插入到table1中,這時select * from table1這個查詢的內容就變成了
field1 field2 field3 field4 field5
1 2 3 4 5
1 2 3 4 5
於是迴圈就移動指標,把第二條記錄複製下來插入到table1中,於是select * from table1這個查詢又增加了一條記錄,於是繼續複製...,就這樣一直繼續下去,直至表被撐滿。
而在ORACLE中,系統指向第一條記錄時,會判斷這條記錄的版本,由於這個版本在查詢select * from table1被提交前就存在,所以它會被複製插入,然後指標移向下一記錄,也就是剛剛加入的這條,判斷它的版本,發現它是在查詢提交後產生的,這時ORACLE就會去找這條記錄在查詢被提交時的版本,得到的結果是當時沒有這條記錄,ORACLE就不認為它是查詢結果中的記錄,於是跳過,再去找下一條記錄。
從上面的處理過程可以看出,SQL server遇到這段代碼,會不停迴圈下去--如果表的記錄數不是限制的話,而ORACLE會把表的記錄複製一次插入就推出。
ORACLE對所有的資料都記錄了它任何時間的狀態,這樣做當然會使資料佔用更大的空間,但也使得查詢得出的資料全部都是同一刻狀態的資料。
舉個例子,如果對於一個銀行的資料庫系統,儲存有儲戶的帳號和儲蓄金額,假設在經理查詢本行所有帳戶的總儲蓄額(假設資料量較大,需要花費的查詢時間較長)時,有使用者做了轉帳處理,就可能出現以下情況:
使用者 金額
A 1400
B 2200
C 600
經理在使用sum統計總金額且沒有使用鎖定,如果已經統計完A和B的金額相加之後,統計到C之前,使用者A將自己帳戶上的金額轉了1000到C的帳戶上,那麼對於某些資料庫系統就可能會出現總金額比實際要多出1000的情況,因為它們在統計到使用者C的儲蓄金額時,會將使用者A轉帳後C的金額當作有效值參與到查詢中,這種結果顯然是不正確的。
而ORACLE在處理到使用者C的記錄時會去找查詢提交時間所對應的版本的記錄,也就是A轉帳到C之前的記錄,顯然這才是經理要得到的結果。
由此看來,ORACLE的多版本,使得在不需要鎖定的情況下,查詢到的資料是同一時間的資料。
這是不是意味著針對ORACLE資料庫開發的查詢就完全不需要鎖來阻塞其他使用者了呢。不是的
以餐館的訂桌系統為例,假設使用者通過查詢來查看各個時間桌子是否被訂了,然後再從沒有被訂的桌子中預定訂某個時間某個桌子的使用權。
如果這個系統是多使用者的,那麼就可能出現這種情況:甲乙兩個使用者同時查看當天下午五點的訂桌情況,然後同時訂下了1號桌。在沒有對其它使用者進行排它阻塞的情況下,兩個使用者同時看到1號桌沒有被訂,於是都下了訂單,於是一張桌子同時被兩人訂了,這顯然出了問題。
正確解決這個問題,顯然還是要使用使用者阻塞的,也就是在查詢時使用for update,這樣在甲使用者查詢時,乙使用者的查詢就會被掛起,直到甲使用者釋放鎖定之後才會執行,就不會出現上面這種情況了。
ORACLE的多版本解決了資料的一致性問題,可以在不加鎖的情況下獲得同一時間的資料,然而這不意味著不需要在讀取表的時候加鎖了,類似上面的這種問題,往往很容易被忽略,而且這種bug很不容易找出來,何時用鎖,何時不用,應當仔細。