數據庫事務的四大特性以及事務的隔離級別


本篇講訴數據庫中事務的四大特性(ACID),並且將會詳細地說明事務的隔離級別與鎖機制。

一、事務的四大特性(ACID)

1、 原子性(Atomicity)

  原子性是指事務包含的所有操作要么全部成功,要么全部失敗回滾,這和前面兩篇博客介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。

2、 一致性(Consistency)

  一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之后都必須處於一致性狀態。

  拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉賬,轉幾次賬,事務結束后兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

3、隔離性(Isolation)

  隔離性是當多個用戶並發訪問數據庫時,比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個並發事務之間要相互隔離。

  即要達到這么一種效果:對於任意兩個並發的事務T1和T2,在事務T1看來,T2要么在T1開始之前就已經結束,要么在T1結束之后才開始,這樣每個事務都感覺不到有其他事務在並發地執行。

  關於事務的隔離性數據庫提供了多種隔離級別,稍后會介紹到。

4、 持久性(Durability)

  持久性是指一個事務一旦被提交了,那么對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。

  例如我們在使用JDBC操作數據庫時,在提交事務方法后,提示用戶事務操作完成,當我們程序執行完成直到看到提示后,就可以認定事務以及正確提交,即使這時候數據庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是數據庫因為故障而沒有執行事務的重大錯誤。

  

  以上介紹完事務的四大特性(簡稱ACID),現在重點來說明下事務的隔離性,當多個線程都開啟事務操作數據庫中的數據時,數據庫系統要能進行隔離操作,以保證各個線程獲取數據的准確性。

Note:

  ①這里簡單理解一個概念:在未提交(commit)前你前面的操作時,更新的都是內存,沒有更新到物理文件中。

      如果未commit就關掉數據庫連接或者后面直接回滾掉,數據庫中的數據並沒有更新。

  ②commit的提交針對的是:DML。

   DML(Data Manipulation Language) 需要提交,這部分是對數據管理操作,比如Insert(插入)、Update(修改)、Delete(刪除);
   DDL (Data Definition Language)不需要提交,這部分是對數據結構定義,比如Create(創建)、Alter(修改)、Drop(刪除)。

  ③SQL語言分為五大類:

   DDL(數據定義語言) - Create、Alter、Drop 這些語句自動提交,無需用Commit提交。

   DQL(數據查詢語言) - Select 查詢語句不存在提交問題。

   DML(數據操縱語言) - Insert、Update、Delete 這些語句需要Commit才能提交。

   DTL(事務控制語言) - Commit、Rollback 事務提交與回滾語句。

   DCL(數據控制語言) - Grant、Revoke 授予權限與回收權限語句。 

二、事務的隔離性

1、我們先看看如果不考慮事務的隔離性,會發生的幾種問題:

(1)臟讀

  臟讀是指在一個事務處理過程里讀取了另一個未提交的事務中的數據。

  當一個事務正在多次修改某個數據,而在這個事務中這多次的修改都還未提交,這時一個並發的事務來訪問該數據,就會造成兩個事務得到的數據不一致。例如:用戶A向用戶B轉賬100元,對應SQL命令如下

update account set money=money+100 where name=’B’;  (此時A通知B)

update account set money=money - 100 where name=’A’;

  當只執行第一條SQL時,A通知B查看賬戶,B發現確實錢已到賬(此時即發生了臟讀),而之后無論第二條SQL是否執行,只要該事務不提交,則所有操作都將回滾,那么當B以后再次查看賬戶時就會發現錢其實並沒有轉。

(2)不可重復讀

  不可重復讀是指在對於數據庫中的某個數據,一個事務范圍內多次查詢卻返回了不同的數據值,這是由於在查詢間隔,被另一個事務修改並提交了。

  例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據並且提交事務給數據庫,事務T1再次讀取該數據就得到了不同的結果,發送了不可重復讀。

  不可重復讀和臟讀的區別是,臟讀是某一事務讀取了另一個事務未提交的臟數據,而不可重復讀則是讀取了前一事務提交的數據。

  在某些情況下,不可重復讀並不是問題,比如我們多次查詢某個數據當然以最后查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對於同一個數據A和B依次查詢就可能不同,A和B就可能打起來了……

(3)虛讀(幻讀)

  幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個數據項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值還是為“1”並且提交給數據庫。而操作事務T1的用戶如果再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺一樣,這就是發生了幻讀。

  幻讀和不可重復讀都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重復讀查詢的都是同一個數據項,可以理解為是針對update 與delete操作。而幻讀針對的是一批數據整體(比如數據的個數),可以理解為是針對insert操作。

2、現在來看看MySQL數據庫為我們提供的四種隔離級別:

  ① Serializable (串行化):可避免臟讀、不可重復讀、幻讀的發生。

  ② Repeatable read (可重復讀):可避免臟讀、不可重復讀的發生。

  ③ Read committed (讀已提交):可避免臟讀的發生。

  ④ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。

  以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別為Repeatable read (可重復讀)。

  注:在mysql5.6以上版本引入了MVCC,在Repeatable read隔離級別下已經不存在幻讀問題了。

  在MySQL數據庫中,支持上面四種隔離級別,默認的為Repeatable read (可重復讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的為Read committed級別。

  在MySQL數據庫中查看當前事務的隔離級別:

 select @@tx_isolation;

  在MySQL數據庫中設置事務的隔離 級別:

set  [glogal | session]  transaction isolation level 隔離級別名稱;

set tx_isolation=’隔離級別名稱;’

例1:查看當前事務的隔離級別:

例2:將事務的隔離級別設置為Read uncommitted級別:

或:

記住:設置數據庫的隔離級別一定要是在開啟事務之前!

  如果是使用JDBC對數據庫的事務設置隔離級別的話,也應該是在調用Connection對象的setAutoCommit(false)方法之前。調用Connection對象的setTransactionIsolation(level)即可設置當前鏈接的隔離級別,至於參數level,可以使用Connection對象的字段:

  在JDBC中設置隔離級別的部分代碼:

  后記:隔離級別的設置只對當前鏈接有效。對於使用MySQL命令窗口而言,一個窗口就相當於一個鏈接,當前窗口設置的隔離級別只對當前窗口中的事務有效;對於JDBC操作數據庫來說,一個Connection對象相當於一個鏈接,而對於Connection對象設置的隔離級別只對該Connection對象有效,與其他鏈接Connection對象無關。

Note:隔離性的實質是通過數據庫的鎖機制來實現的。

三、Mysql數據庫的鎖機制

一般可以分為兩類,一個是悲觀鎖,一個是樂觀鎖,悲觀鎖一般就是我們通常說的數據庫鎖機制,樂觀鎖一般是指用戶自己實現的一種鎖機制,比如hibernate實現的樂觀鎖甚至編程語言也有樂觀鎖的思想的應用。

1、悲觀鎖

悲觀鎖:顧名思義,就是很悲觀,它對於數據被外界修改持保守態度,認為數據隨時會修改,所以整個數據處理中需要將數據加鎖。悲觀鎖一般都是依靠關系數據庫提供的鎖機制,事實上關系數據庫中的行鎖,表鎖不論是讀寫鎖都是悲觀鎖。

1)悲觀鎖按照使用性質划分:

  • 共享鎖(Share locks簡記為S鎖):也稱讀鎖,事務A對對象T加s鎖,其他事務也只能對T加S,多個事務可以同時讀,但不能有寫操作,直到A釋放S鎖。

  • 排它鎖(Exclusivelocks簡記為X鎖):也稱寫鎖,事務A對對象T加X鎖以后,其他事務不能對T加任何鎖,只有事務A可以讀寫對象T直到A釋放X鎖。

  • 更新鎖(簡記為U鎖):用來預定要對此對象施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的對象將要被更新時,則升級為X鎖,主要是用來防止死鎖的。因為使用共享鎖時,修改數據的操作分為兩步,首先獲得一個共享鎖,讀取數據,然后將共享鎖升級為排它鎖,然后再執行修改操作。這樣如果同時有兩個或多個事務同時對一個對象申請了共享鎖,在修改數據的時候,這些事務都要將共享鎖升級為排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個數據在修改前直接申請更新鎖,在數據修改的時候再升級為排它鎖,就可以避免死鎖。

2)悲觀鎖按照作用范圍划分:

  • 行鎖:鎖的作用范圍是行級別,數據庫能夠確定那些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖。舉個例子,一個用戶表user,有主鍵id和用戶生日birthday當你使用update … where id=?這樣的語句數據庫明確知道會影響哪一行,它就會使用行鎖,當你使用update … where birthday=?這樣的的語句的時候因為事先不知道會影響哪些行就可能會使用表鎖。
  • 表鎖:鎖的作用范圍是整張表。

2、樂觀鎖

樂觀鎖:顧名思義,就是很樂觀,每次自己操作數據的時候認為沒有人回來修改它,所以不去加鎖,但是在更新的時候會去判斷在此期間數據有沒有被修改,需要用戶自己去實現。既然都有數據庫提供的悲觀鎖可以方便使用為什么要使用樂觀鎖呢?對於讀操作遠多於寫操作的時候,大多數都是讀取,這時候一個更新操作加鎖會阻塞所有讀取,降低了吞吐量。最后還要釋放鎖,鎖是需要一些開銷的,我們只要想辦法解決極少量的更新操作的同步問題。換句話說,如果是讀寫比例差距不是非常大或者你的系統沒有響應不及時,吞吐量瓶頸問題,那就不要去使用樂觀鎖,它增加了復雜度,也帶來了額外的風險。

1)樂觀鎖實現方式:

  • 版本號(記為version):就是給數據增加一個版本標識,在數據庫上就是表中增加一個version字段,每次更新把這個字段加1,讀取數據的時候把version讀出來,更新的時候比較version,如果還是開始讀取的version就可以更新了,如果現在的version比老的version大,說明有其他事務更新了該數據,並增加了版本號,這時候得到一個無法更新的通知,用戶自行根據這個通知來決定怎么處理,比如重新開始一遍。這里的關鍵是判斷version和更新兩個動作需要作為一個原子單元執行,否則在你判斷可以更新以后正式更新之前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務做的更新,造成第二類丟失更新,所以你可以使用update … where … and version=”old version”這樣的語句,根據返回結果是0還是非0來得到通知,如果是0說明更新沒有成功,因為version被改了,如果返回非0說明更新成功。
  • 時間戳(timestamp):和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用數據庫服務器的時間戳不能是業務系統的時間。
  • 待更新字段:和版本號方式相似,只是不增加額外字段,直接使用有效數據字段做版本控制信息,因為有時候我們可能無法改變舊系統的數據庫表結構。假設有個待更新字段叫count,先去讀取這個count,更新的時候去比較數據庫中count的值是不是我期望的值(即開始讀的值),如果是就把我修改的count的值更新到該字段,否則更新失敗。java的基本類型的原子類型對象如AtomicInteger就是這種思想。
  • 所有字段:和待更新字段類似,只是使用所有字段做版本控制信息,只有所有字段都沒變化才會執行更新。

2)樂觀鎖幾種方式的區別:

新系統設計可以使用version方式和timestamp方式,需要增加字段,應用范圍是整條數據,不論那個字段修改都會更新version,也就是說兩個事務更新同一條記錄的兩個不相關字段也是互斥的,不能同步進行。舊系統不能修改數據庫表結構的時候使用數據字段作為版本控制信息,不需要新增字段,待更新字段方式只要其他事務修改的字段和當前事務修改的字段沒有重疊就可以同步進行,並發性更高。

 Note:

  第一類丟失更新(Update Lost):此種更新丟失是因為回滾的原因,所以也叫回滾丟失。此時兩個事務同時更新count,兩個事務都讀取到100,事務一更新成功並提交,count=100+1=101,事務二出於某種原因更新失敗了,然后回滾,事務二就把count還原為它一開始讀到的100,此時事務一的更新就這樣丟失了。

  第二類丟失更新(Second Update Lost):此種更新丟失是因為更新被其他事務給覆蓋了,也可以叫覆蓋丟失。舉個例子,兩個事務同時更新count,都讀取100這個初始值,事務一先更新成功並提交,count=100+1=101,事務二后更新成功並提交,count=100+1=101,由於事務二count還是從100開始增加,事務一的更新就這樣丟失了。

四、MySql的InnoDB一致性讀(快照讀) 

  數據庫讀,是數據庫操作中很常見的一個操作,在數據庫事務中也經常出現讀取數據的操作,比如先讀取是否存在,然后不存在就插入等,想要了解數據庫事務,理解“讀”這個操作必不可少。

  數據庫讀分為:一致非鎖定讀、鎖定讀。這里是mysql官方文檔對於一致性讀的講解,翻譯一下。

1、首先來個小總結:

  1. 一致非鎖定讀,也可以稱為快照讀,其實就是普通的讀取即普通SELECT語句。
  2. 既然是快照讀,故 SELECT 的時候,會生成一個快照。
  3. 生成快照的時機:事務中第一次調用SELECT語句的時候才會生成快照,在此之前事務中執行的update、insert、delete操作都不會生成快照。
  4. 不同事務隔離級別下,快照讀的區別: READ COMMITTED 隔離級別下,每次讀取都會重新生成一個快照,所以每次快照都是最新的,也因此事務中每次SELECT也可以看到其它已commit事務所作的更改;REPEATED READ 隔離級別下,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對數據進行更改才會更新快照,因此,只有第一次SELECT之前其它已提交事務所作的更改你可以看到,但是如果已執行了SELECT,那么其它事務commit數據,你SELECT是看不到的。

2、對照官方原文翻譯理解

下面是翻譯正文(原文地址:https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)

  A consistent read means that  InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point of time, and no changes made by later or uncommitted transactions. The exception to this rule is that the query sees the changes made by earlier statements within the same transaction. This exception causes the following anomaly: If you update some rows in a table, a  SELECT sees the latest version of the updated rows, but it might also see older versions of any rows. If other sessions simultaneously update the same table, the anomaly means that you might see the table in a state that never existed in the database.
  一致性讀意味着InnoDB引擎使用多版本展示某個時間點的數據庫的查詢快照(一致性讀是通過 MVCC(多版本並發控制) 為查詢提供了一個基於時間的點的快照)。查詢可以看到自己之前已提的所有事務所做的更改,看不到在查詢開始之后的事務提交的更改(ps:可能會有疑問,READ COMMITTED隔離級別下是可以看到事務提交的更改的,這個疑問文檔下面會有一句話解析,其實這是因為:READ COMMITTED 隔離級別下每一次查詢都會提前更新數據庫快照!)或者未提交的事務所做的更改。這個規則的例外情況是查詢可以看到同一個事務中早期語句所作的更改。這個例外導致了一下異常現象:
如果更新表中的一些行,SELECT 語句看到更新行的最新版本,但是也可能看到任何行的舊版本(ps:這后半句是什么意思?)
如果有其它會話同時更新了同一張表,這個異常意味着你可能看到數據庫中從未存在過的表的臟數據。

  If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction. You can get a fresher snapshot for your queries by committing the current transaction and after that issuing new queries.

  With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.

Consistent read is the default mode in which InnoDB processes SELECT statements in READ COMMITTED and REPEATABLE READ isolation levels. A consistent read does not set any locks on the tables it accesses, and therefore other sessions are free to modify those tables at the same time a consistent read is being performed on the table.

  如果事務隔離級別是 REPEATABLE READ (默認級別),同一個事務中的所有一致性讀都會讀取此事務中第一次一致性讀所生成的快照。你可以通過提交當前事務然后進行一個新的查詢來得到一個最新的查詢快照。
使用 READ COMMITTED 隔離級別,事務中每次一致性讀都會設置並讀取當前最新的快照。

  在 READ COMMITTED 和 REPEATED READ 隔離級別下,一致性讀是InnoDB 執行 SELECT 語句 的默認方式。一致性讀不會對表的訪問設置任何鎖,因此其它的會話可以同時改變一張正在進行一致性讀的表。

 

  Suppose that you are running in the default REPEATABLE READ isolation level. When you issue a consistent read (that is, an ordinary SELECT statement), InnoDB gives your transaction a timepoint according to which your query sees the database. If another transaction deletes a row and commits after your timepoint was assigned, you do not see the row as having been deleted. Inserts and updates are treated similarly.

  設想一下事務正在默認的 REPEATABLE READ 隔離級別下運行。當你進行一個一致性讀(也就是說,一個普通的 SELECT 語句),InnoDB 會根據這次查詢看到的數據庫內容設置一個時間點。如果另外一個事務在這個時間點之后刪除了其中一行並且提交,你不會看到這樣被刪除了。插入和更新也是被同樣對待。
(ps:就是說你先在事務1中select,然后事務2中進行delete、insert、update操作,然后再次再事務1中進行select 你是看不到delete、insert、update的結果的,因為第一次select已經形成了快照)

ps:上面這個例子可能沒看太明白,解釋一下,其實有兩個事務作對比就比較好理解了,不過文檔上這里只貼出來了事務1,其它事務(比如事務2、事務3等)並沒有貼出來作對比。上面第一個例子的意思就是說:查詢xyz的時候沒有查詢到,但是delete的時候卻刪除了一些行,這是因為有其它事務修改了數據;第二個例子是說:查詢abc的時候沒有查詢到,但是update的時候卻更新到了,然后由於是本事務進行的更新,故而后續的查詢都可以看到本事務所作的更改。總結下這兩個例子想要表達的就是:REPEATED READ 隔離級別下,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對數據進行更改才會更新快照,因此,只有第一次SELECT之前其它已提交事務所作的更改你可以看到,但是如果已執行了SELECT,那么其它事務commit數據,你SELECT是看不到的。

 

  You can advance your timepoint by committing your transaction and then doing another SELECT or START TRANSACTION WITH CONSISTENT SNAPSHOT.

  可以通過提交事務將時間點提前,然后執行另一個 SELECT 或者 START TRANSACTION WITH CONSISTENT SNAPSHOT。

  This is called multi-versioned concurrency control.

  這被稱為 多版本並發控制 (MVCC)。

 

  In the following example, session A sees the row inserted by B only when B has committed the insert and A has committed as well, so that the timepoint is advanced past the commit of B.

  接下來的例子,會話A可以看到B插入的數據,只有B已經提交了插入的數據並且A也已經提交了事務的時候,這是由於快照生成的時間點在B提交之前。

  If you want to see the freshest” state of the database, use either the READ COMMITTED isolation level or a locking read:

  如果想要看到數據庫最新的狀態,需要使用 READ COMMITTED 隔離級別或者鎖定讀:

SELECT * FROM TABLE FOR SHARE;

 

  With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot. With LOCK IN SHARE MODE, a locking read occurs instead: A SELECT blocks until the transaction containing the freshest rows ends (see Section 14.5.2.4, “Locking Reads”).

  READ COMMITTED 隔離級別下,事務中每次一致性讀都會設置並讀取最新的快照。使用 LOCK IN SHARE MODE 模式,會進行鎖定讀:SELECT 會阻塞直到事務讀取到最新的行 為止。

  
一致性讀對DDL(Data definition language)語句無效:
  • 一致性讀對 DROP TABLE 無效,因為MySQL不能使用已經被刪除的表,InnoDB會銷毀表。
  • 一致性讀對 ALTER TABLE 無效,這是因為這條語句會根據源表產生一個臨時副本並且會刪除源表當副本被創建時。當你在事務中再次進行一致讀的時候,新表中的行對你是不可見的,因為當事務快照創建的時候這些行還不存在。因此,事務會返回錯誤: ER_TABLE_DEF_CHANGED, “Table definition has changed, please retry transaction”。

五、select for update/lock in share mode 對事務並發性影響

1、for update, lock in share mode

for update :是IX鎖(意向排它鎖),即在符合條件的rows上都加了排它鎖,其他session也就無法在這些記錄上添加任何的S鎖或X鎖。如果不存在一致性非鎖定讀的話,那么其他session是無法讀取和修改這些記錄的,但是innodb有非鎖定讀(快照讀並不需要加鎖),for update之后並不會阻塞其他session的快照讀取操作,除了select ...lock in share mode和select ... for update這種顯示加鎖的查詢操作。

lock in share mode :是IS鎖(意向共享鎖),即在符合條件的rows上都加了共享鎖,這樣的話,其他session可以讀取這些記錄,也可以繼續添加IS鎖,但是無法修改這些記錄直到你這個加鎖的session執行完成(否則直接鎖等待超時)。 

ps: 這兩種模式加鎖的表無數據的情況下,鎖不會起作用,必須加鎖的表必須有數據(待親測)。 lock in share mode 也叫間隙鎖。

2、事務並發性理解

事務並發性,粗略的理解就是單位時間內能夠執行的事務數量,常見的單位是 TPS( transactions per second).

那在數據量和業務操作量一定的情況下,常見的提高事務並發性主要考慮的有哪幾點呢?

1)1.提高服務器的處理能力,讓事務的處理時間變短。

  這樣不僅加快了這個事務的執行時間,也降低了其他等待該事務執行的事務執行時間。

2)盡量將事務涉及到的 sql 操作語句控制在合理范圍,換句話說就是不要讓一個事務包含的操作太多或者太少。

  在業務繁忙情況下,如果單個事務操作的表或者行數據太多,其他的事務可能都在等待該事務 commit或者 rollback,這樣會導致整體上的 TPS 降低。但是,如果每個 sql 語句都是一個事務也是不太現實的。一來,有些業務本身需要多個sql語句來構成一個事務(比如匯款這種多個表的操作);二來,每個 sql 都需要commit,如果在 mysql 里 innodb_flush_log_at_trx_commit=1 的情況下,會導致 redo log 的刷新過於頻繁,也不利於整體事務數量的提高(IO限制也是需要考慮的重要因素)。

3)在操作的時候,盡量控制鎖的粒度,能用小的鎖粒度就盡量用鎖的粒度,用完鎖資源后要記得立即釋放,避免后面的事務等待。

  但是有些情況下,由於業務需要,或者為了保證數據的一致性的時候,必須要增加鎖的粒度,這個時候就是下面所說的幾種情況。

3、select for update 理解

select col from t where where_clause for update 的目的是在執行這個 select 查詢語句的時候,會將對應的索引訪問條目進行上排他鎖(X 鎖),也就是說這個語句對應的鎖就相當於update帶來的效果。

那這種語法為什么會存在呢?肯定是有需要這種方式的存在啦!!請看下面的案例描述:

案例1:

前提條件:

mysql 隔離級別 repeatable-read ,

事務1:

建表:
CREATE TABLE `lockt` (
  `id` int(11) NOT NULL,
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `col1_ind` (`col1`),
  KEY `col2_ind` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

插入數據 。。。。。

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |   14 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

然后另外一個事務2 進行了下面的操作:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |   14 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

mysql> update lockt set  col2= 144  where col2=14;  
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

結果:可以看到事務2 將col2=14 的列改為了 col2=144.

可是事務1繼續執行的時候根本沒有覺察到 lockt 發生了變化,請看 事務1 繼續后面的操作:

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |   14 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.01 sec)

mysql> update lockt set  col2=col2*2  where col2=14;    
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |  144 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

結果: 事務1 明明查看到的存在 col2=12 的行數據,可是 update 后,竟然不僅沒有改為他想要的col2=28 的值,反而變成了 col2=144 !!!!

這在有些業務情況下是不允許的,因為有些業務希望我通過 select * from lockt; 查詢到的數據是此時數據庫里面真正存儲的最新數據,並且不允許其他的事務來修改只允許我來修改。(這個要求很霸氣,但是我喜歡。。

這種情況就是很牛逼的情況了。具體的細節請參考下面的案例2:

案例2:

mysql 條件和案例1 一樣。

事務1操作:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from lockt where col2=20 for update;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  8 |    8 |   20 |
+----+------+------+
1 row in set (0.00 sec)

事務2 操作:

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |  144 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

mysql> update lockt set  col2=222  where col2=20; 

注意: 事務2 在執行 update lockt set col2=222 where col2=20; 的時候,會發現 sql 語句被 block住了,為什么會發現這種情況呢?

因為事務1 的 select * from lockt where col2=20 for update; 語句會將 col2=20 這個索引的入口給鎖住了,(其實有些時候是范圍的索引條目也被鎖住了,暫時不討論。),那么事務2雖然看到了所有的數據,但是想去修改 col2=20 的行數據的時候, 事務1 只能說 “不可能也不允許”。

后面只有事務1 commit或者rollback 以后,事務2 的才能夠修改 col2=20 的這個行數據。

總結:

這就是 select for update 的使用場景,為了避免自己看到的數據並不是數據庫存儲的最新數據並且看到的數據只能由自己修改,需要用 for update 來限制。

 

4、select lock in share mode 理解

如果看了前面的 select *** for update ,就可以很好的理解 select lock in share mode ,in share mode 子句的作用就是將查找到的數據加上一個 share 鎖,這個就是表示其他的事務只能對這些數據進行簡單的select 操作,並不能夠進行 DML 操作。

那它和 for update 在引用場景上究竟有什么實質上的區別呢?

lock in share mode 沒有 for update 那么霸道,所以它有時候也會遇到問題,請看案例3

案例3:

mysql 環境和案例1 類似

事務1:

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |  144 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

mysql> select * from lockt where col2=20 lock in share mode;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  8 |    8 |   20 |
+----+------+------+
1 row in set (0.00 sec)

事務2 接着開始操作

mysql> select * from lockt;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    3 |
|  5 |    5 |    5 |
|  6 |    6 |    9 |
|  7 |    7 |  144 |
|  8 |    8 |   20 |
+----+------+------+
6 rows in set (0.00 sec)

mysql> select * from lockt where col2=20 lock in share mode;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  8 |    8 |   20 |
+----+------+------+
1 row in set (0.01 sec)

后面的比較蛋疼的一幕出現了,當 事務1 想更新 col2=20 的時候,他發現 block 住了。

mysql> update lockt set col2=22 where col2=20;

解釋:因為事務1 和事務2 都對該行上了一個 share 鎖,事務1 以為就只有自己一個人上了 S 鎖,所以當事務一想修改的時候發現沒法修改,這種情況下,事務1 需要使用 for update 子句來進行約束了,而不是使用 for share 來使用。

5、可能用到的情景和對性能的影響

使用情景:

  1. select *** for update 的使用場景

    為了讓自己查到的數據確保是最新數據,並且查到后的數據只允許自己來修改的時候,需要用到 for update 子句。

  2. select *** lock in share mode 使用場景

    為了確保自己查到的數據沒有被其他的事務正在修改,也就是說確保查到的數據是最新的數據,並且不允許其他人來修改數據。但是自己不一定能夠修改數據,因為有可能其他的事務也對這些數據 使用了 in share mode 的方式上了 S 鎖。


性能影響:

  select for update 語句,相當於一個 update 語句。在業務繁忙的情況下,如果事務沒有及時的commit或者rollback 可能會造成其他事務長時間的等待,從而影響數據庫的並發使用效率。

  select lock in share mode 語句是一個給查找的數據上一個共享鎖(S 鎖)的功能,它允許其他的事務也對該數據上 S鎖,但是不能夠允許對該數據進行修改。如果不及時的commit 或者rollback 也可能會造成大量的事務等待。

  for update 和 lock in share mode 的區別:前一個上的是排他鎖(X 鎖),一旦一個事務獲取了這個鎖,其他的事務是沒法在這些數據上執行 for update ;后一個是共享鎖,多個事務可以同時的對相同數據執行 lock in share mode。

 

 

本文整理自:

https://www.cnblogs.com/fjdingsd/p/5273008.html

https://blog.csdn.net/aluomaidi/article/details/52460844

https://blog.csdn.net/cxm19881208/article/details/79415726

https://blog.csdn.net/liangzhonglin/article/details/65438777

https://www.cnblogs.com/liushuiwuqing/p/3966898.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM