共計 5231 個字符,預計需要花費 14 分鐘才能閱讀完成。
本篇內(nèi)容主要講解“sqlserver 中的事務和鎖的詳細介紹”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓丸趣 TV 小編來帶大家學習“sqlserver 中的事務和鎖的詳細介紹”吧!
一、臟讀、不可重復讀、幻讀
(1)臟讀:臟讀就是指當一個事務正在訪問數(shù)據(jù),并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時,另外一個事務也訪問這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。
例如:
張三的工資為 5000, 事務 A 中把他的工資改為 8000, 但事務 A 尚未提交。
與此同時,
事務 B 正在讀取張三的工資,讀取到張三的工資為 8000。
隨后,
事務 A 發(fā)生異常,而回滾了事務。張三的工資又回滾為 5000。
最后,
事務 B 讀取到的張三工資為 8000 的數(shù)據(jù)即為臟數(shù)據(jù),事務 B 做了一次臟讀。
(2)不可重復讀:是指在一個事務內(nèi),多次讀同一數(shù)據(jù)。在這個事務還沒有結(jié)束時,另外一個事務也訪問該同一數(shù)據(jù)。那么,在第一個事務中的兩次讀數(shù)據(jù)之間,由于第二個事務的修改,那么第一個事務兩次讀到的的數(shù)據(jù)可能是不一樣的。這樣就發(fā)生了在一個事務內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,因此稱為是不可重復讀。
例如:
在事務 A 中,讀取到張三的工資為 5000,操作沒有完成,事務還沒提交。
與此同時,
事務 B 把張三的工資改為 8000,并提交了事務。
隨后,
在事務 A 中,再次讀取張三的工資,此時工資變?yōu)?8000。在一個事務中前后兩次讀取的結(jié)果并不致,導致了不可重復讀。
(3)幻讀: 是指當事務不是獨立執(zhí)行時發(fā)生的一種現(xiàn)象,例如第一個事務對一個表中的數(shù)據(jù)進行了修改,這種修改涉及到表中的全部數(shù)據(jù)行。同時,第二個事務也修改這個表中的數(shù)據(jù),這種修改是向表中插入一行新數(shù)據(jù)。那么,以后就會發(fā)生操作第一個事務的用戶發(fā)現(xiàn)表中還有沒有修改的數(shù)據(jù)行,就好象發(fā)生了幻覺一樣。
例如:
目前工資為 5000 的員工有 10 人,事務 A 讀取所有工資為 5000 的人數(shù)為 10 人。
此時,
事務 B 插入一條工資也為 5000 的記錄。
這是,事務 A 再次讀取工資為 5000 的員工,記錄為 11 人。此時產(chǎn)生了幻讀。
不可重復讀的重點是修改: 同樣的條件,你讀取過的數(shù)據(jù),再次讀取出來發(fā)現(xiàn)值不一樣了 幻讀的重點在于新增或者刪除:同樣的條件,第 1 次和第 2 次讀出來的記錄數(shù)不一樣
二、獨占鎖、共享鎖、更新鎖,樂觀鎖、悲觀鎖
1、鎖的兩種分類方式
(1)從數(shù)據(jù)庫系統(tǒng)的角度來看,鎖分為以下三種類型:
?獨占鎖(Exclusive Lock)獨占鎖鎖定的資源只允許進行鎖定操作的程序使用,其它任何對它的操作均不會被接受。執(zhí)行數(shù)據(jù)更新命令,即 INSERT、UPDATE 或 DELETE 命令時,SQL Server 會自動使用獨占鎖。但當對象上有其它鎖存在時,無法對其加獨占鎖。獨占鎖一直到事務結(jié)束才能被釋放。
?共享鎖(Shared Lock)共享鎖鎖定的資源可以被其它用戶讀取,但其它用戶不能修改它。在 SELECT 命令執(zhí)行時,SQL Server 通常會對對象進行共享鎖鎖定。通常加共享鎖的數(shù)據(jù)頁被讀取完畢后,共享鎖就會立即被釋放。
?更新鎖(Update Lock)更新鎖是為了防止死鎖而設立的。當 SQL Server 準備更新數(shù)據(jù)時,它首先對數(shù)據(jù)對象作更新鎖鎖定,這樣數(shù)據(jù)將不能被修改,但可以讀取。等到 SQL Server 確定要進行更新數(shù)據(jù)操作時,它會自動將更新鎖換為獨占鎖。但當對象上有其它鎖存在時,無法對其作更新鎖鎖定。
(2)從程序員的角度看,鎖分為以下兩種類型:
?悲觀鎖(Pessimistic Lock)悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務,以及來自外部系統(tǒng)的事務處理)修改持保守態(tài)度,因此在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。?
?樂觀鎖(Optimistic Lock)相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本(Version)記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來實現(xiàn)。讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應記錄的當前版本信息進行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當前版本號,則予以更新,否則認為是過期數(shù)據(jù)。?
2、數(shù)據(jù)庫中如何使用鎖
首先從悲觀鎖開始說。在 SqlServer 等其余很多數(shù)據(jù)庫中,數(shù)據(jù)的鎖定通常采用頁級鎖的方式,也就是說對一張表內(nèi)的數(shù)據(jù)是一種串行化的更新插入機制,在任何時間同一張表只會插 1 條數(shù)據(jù),別的想插入的數(shù)據(jù)要等到這一條數(shù)據(jù)插完以后才能依次插入。帶來的后果就是性能的降低,在多用戶并發(fā)訪問的時候,當對一張表進行頻繁操作時,會發(fā)現(xiàn)響應效率很低,數(shù)據(jù)庫經(jīng)常處于一種假死狀態(tài)。而 Oracle 用的是行級鎖,只是對想鎖定的數(shù)據(jù)才進行鎖定,其余的數(shù)據(jù)不相干,所以在對 Oracle 表中并發(fā)插數(shù)據(jù)的時候,基本上不會有任何影響。
注:對于悲觀鎖是針對并發(fā)的可能性比較大,而一般在我們的應用中用樂觀鎖足以。
Oracle 的悲觀鎖需要利用一條現(xiàn)有的連接,分成兩種方式,從 SQL 語句的區(qū)別來看,就是一種是 for update,一種是 for update nowait 的形式。比如我們看一個例子。
首先建立測試用的數(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 表中。
(1)for update 形式介紹
然后我們看一下 for update 鎖定方式。我們執(zhí)行如下的 select for update 語句:
select * from test where id = 10 for update
通過這條檢索語句鎖定以后,再開另外一個 sql*plus 窗口進行操作,再把上面這條 sql 語句執(zhí)行一便,你會發(fā)現(xiàn) sqlplus 好像死在那里了,好像檢索不到數(shù)據(jù)的樣子,但是也不返回任何結(jié)果,就屬于卡在那里的感覺。這個時候是什么原因呢,就是一開始的第一個 Session 中的 select for update 語句把數(shù)據(jù)鎖定住了。由于這里鎖定的機制是 wait 的狀態(tài) (只要不表示 nowait 那就是 wait),所以第二個 Session(也就是卡住的那個 sql*plus) 中當前這個檢索就處于等待狀態(tài)。當?shù)谝粋€ session 最后 commit 或者 rollback 之后,第二個 session 中的檢索結(jié)果就是自動跳出來,并且也把數(shù)據(jù)鎖定住。
不過如果你第二個 session 中你的檢索語句如下所示:select * from test where id = 10,也就是沒有 for update 這種鎖定數(shù)據(jù)的語句的話,就不會造成阻塞了。
(2)for update nowait 形式介紹
另外一種情況,就是當數(shù)據(jù)庫數(shù)據(jù)被鎖定的時候,也就是執(zhí)行剛才 for update 那條 sql 以后,我們在另外一個 session 中執(zhí)行 for update nowait 后又是什么樣呢。
比如如下的 sql 語句:
select * from test where id = 10 for update nowait
由于這條語句中是制定采用 nowait 方式來進行檢索,所以當發(fā)現(xiàn)數(shù)據(jù)被別的 session 鎖定中的時候,就會迅速返回 ORA-00054 錯誤,內(nèi)容是資源正忙, 但指定以 NOWAIT 方式獲取資源。所以在程序中我們可以采用 nowait 方式迅速判斷當前數(shù)據(jù)是否被鎖定中,如果鎖定中的話,就要采取相應的業(yè)務措施進行處理。
那這里另外一個問題,就是當我們鎖定住數(shù)據(jù)的時候,我們對數(shù)據(jù)進行更新和刪除的話會是什么樣呢。
比如同樣,我們讓第一個 Session 鎖定住 id=10 的那條數(shù)據(jù),我們在第二個 session 中執(zhí)行如下語句:
update test set value=2 where id = 10
這個時候我們發(fā)現(xiàn) update 語句就好像 select for update 語句一樣也停住卡在這里,當你第一個 session 放開鎖定以后 update 才能正常運行。當你 update 運行后,數(shù)據(jù)又被你 update 語句鎖定住了,這個時候只要你 update 后還沒有 commit,別的 session 照樣不能對數(shù)據(jù)進行鎖定更新等等。
總之,Oracle 中的悲觀鎖就是利用 Oracle 的 Connection 對數(shù)據(jù)進行鎖定。在 Oracle 中,用這種行級鎖帶來的性能損失是很小的,只是要注意程序邏輯,不要給你一不小心搞成死鎖了就好。而且由于數(shù)據(jù)的及時鎖定,在數(shù)據(jù)提交時候就不呼出現(xiàn)沖突,可以省去很多惱人的數(shù)據(jù)沖突處理。缺點就是你必須要始終有一條數(shù)據(jù)庫連接,就是說在整個鎖定到最后放開鎖的過程中,你的數(shù)據(jù)庫聯(lián)接要始終保持住。
與悲觀鎖相對的,我們有了樂觀鎖。樂觀鎖一開始也說了,就是一開始假設不會造成數(shù)據(jù)沖突,在最后提交的時候再進行數(shù)據(jù)沖突檢測。
在樂觀鎖中,我們有 3 種常用的做法來實現(xiàn):
?第一種就是在數(shù)據(jù)取得的時候把整個數(shù)據(jù)都 copy 到應用中,在進行提交的時候比對當前數(shù)據(jù)庫中的數(shù)據(jù)和開始的時候更新前取得的數(shù)據(jù)。
當發(fā)現(xiàn)兩個數(shù)據(jù)一模一樣以后,就表示沒有沖突可以提交,否則則是并發(fā)沖突,需要去用業(yè)務邏輯進行解決。
?第二種樂觀鎖的做法就是采用版本戳,這個在 Hibernate 中得到了使用。
采用版本戳的話,首先需要在你有樂觀鎖的數(shù)據(jù)庫 table 上建立一個新的 column,比如為 number 型,當你數(shù)據(jù)每更新一次的時候,版本數(shù)就會往上增加 1。
比如同樣有 2 個 session 同樣對某條數(shù)據(jù)進行操作。兩者都取到當前的數(shù)據(jù)的版本號為 1,當?shù)谝粋€ session 進行數(shù)據(jù)更新后,在提交的時候查看到當前數(shù)據(jù)的版本還為 1,和自己一開始取到的版本相同。就正式提交,然后把版本號增加 1,這個時候當前數(shù)據(jù)的版本為 2。當?shù)诙€ session 也更新了數(shù)據(jù)提交的時候,發(fā)現(xiàn)數(shù)據(jù)庫中版本為 2,和一開始這個 session 取到的版本號不一致,就知道別人更新過此條數(shù)據(jù),這個時候再進行業(yè)務處理,比如整個 Transaction 都 Rollback 等等操作。
在用版本戳的時候,可以在應用程序側(cè)使用版本戳的驗證,也可以在數(shù)據(jù)庫側(cè)采用 Trigger(觸發(fā)器)來進行驗證。不過數(shù)據(jù)庫的 Trigger 的性能開銷還是比較的大,所以能在應用側(cè)進行驗證的話還是推薦不用 Trigger。
?第三種做法和第二種做法有點類似,就是也新增一個 Table 的 Column,不過這次這個 column 是采用 timestamp 型,存儲數(shù)據(jù)最后更新的時間。
在 Oracle9i 以后可以采用新的數(shù)據(jù)類型,也就是 timestamp with time zone 類型來做時間戳。這種 Timestamp 的數(shù)據(jù)精度在 Oracle 的時間類型中是最高的,精確到微秒(還沒與到納秒的級別),一般來說,加上數(shù)據(jù)庫處理時間和人的思考動作時間,微秒級別是非常非常夠了,其實只要精確到毫秒甚至秒都應該沒有什么問題。
和剛才的版本戳類似,也是在更新提交的時候檢查當前數(shù)據(jù)庫中數(shù)據(jù)的時間戳和自己更新前取到的時間戳進行對比,如果一致則 OK,否則就是版本沖突。如果不想把代碼寫在程序中或者由于別的原因無法把代碼寫在現(xiàn)有的程序中,也可以把這個時間戳樂觀鎖邏輯寫在 Trigger 或者存儲過程中。
三、事務五種隔離級別
Isolation 屬性一共支持五種事務設置,具體介紹如下:
(1)DEFAULT
使用數(shù)據(jù)庫設置的隔離級別(默認),由 DBA 默認的設置來決定隔離級別。
(2)READ_UNCOMMITTED
這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數(shù)據(jù)。
會出現(xiàn)臟讀、不可重復讀、幻讀(隔離級別最低,并發(fā)性能高)。
(3)READ_COMMITTED
保證一個事務修改的數(shù)據(jù)提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數(shù)據(jù)。
可以避免臟讀,但會出現(xiàn)不可重復讀、幻讀問題(鎖定正在讀取的行)。
(4)REPEATABLE_READ
可以防止臟讀、不可重復讀,但會出幻讀(鎖定所讀取的所有行)。
(5)SERIALIZABLE
這是花費最高代價但是最可靠的事務隔離級別,事務被處理為順序執(zhí)行。
保證所有的情況不會發(fā)生(鎖表)。
到此,相信大家對“sqlserver 中的事務和鎖的詳細介紹”有了更深的了解,不妨來實際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!