MySQL事務隔離級別和一致性詳解
一、如何理解隔離性
MySQL服務可能會同時被多個客戶端進程(線程)訪問,訪問的方式以事務方式進行
一個事務可能由多條SQL構成,也就意味著,任何一個事務,都有執(zhí)行前,執(zhí)行中,執(zhí)行后的階段。而所謂的原子性,其實就是讓用戶層,要么看到執(zhí)行前,要么看到執(zhí)行后。執(zhí)行中出現(xiàn)問題,可以隨時回滾。所以單個事務,對用戶表現(xiàn)出來的特性,就是原子性。
但,畢竟所有事務都要有個執(zhí)行過程,那么在多個事務各自執(zhí)行多個SQL的時候,就還是有可能會出現(xiàn)互相影響的情況。比如:多個事務同時訪問同一張表,甚至同一行數(shù)據(jù)。 就如同你媽媽給你說:你要么別學,要學就學到最好。至于你怎么學,中間有什么困難,你媽媽不關心。那么 你的學習,對你媽媽來講,就是原子的。那么你學習過程中,很容易受別人干擾,此時,就需要將你的學習隔離開,保證你的學習環(huán)境是健康的。
- 數(shù)據(jù)庫中,為了保證事務執(zhí)行過程中盡量不受干擾,就有了一個重要特征:隔離性
- 數(shù)據(jù)庫中,允許事務受不同程度的干擾,就有了一種重要特征:隔離級別
二、隔離性的體現(xiàn)
講個小故事,如果同一時間有一個update操作和一個select操作,那么哪一個先執(zhí)行呢??如果是update先執(zhí)行,那么select拿到的就是最新的數(shù)據(jù),而如果是select先執(zhí)行,那么拿到的是老的數(shù)據(jù),大多數(shù)人肯定會覺得肯定要讓update先執(zhí)行而看到新數(shù)據(jù),但實際上這種想法是不太合理的。比方拿時間線來說,我們的父母出生得比我們早,那么直到他們生下我的這段時間里,我是看不到這個世界的,我只看得到自己出生之后所在時間線的世界,而以前已經(jīng)過世的親人也看不到我現(xiàn)在所看到的世界…… 這就說明了我們站在上帝視角,每一個人都只能看到自己活著時候世界所對應的樣子,所以我們時間線一直在延展的時候,每個人看到的世界都應該是不一樣的,這才符合自然規(guī)律,人與人之間具有隔離性,因此讓每一個事務都看到最新的數(shù)據(jù)是不合理的,而是應該讓每一個事務在他到來時看到他應該看到的數(shù)據(jù)。這就是隔離性的體現(xiàn)。(所以updata和select誰先跑取決于誰先來)
可是先到來的并不一定先退出,所以即使我先到來了但是操作時間很長,你在我操作結束之前也是看不到我更新后的數(shù)據(jù)的,這也是隔離性的體現(xiàn)。
舉個例子,你的朋友要轉賬100給你,在你朋友給你轉錢的時候,你一定是得等他轉錢事務都完成了才能查到。這是正常的隔離性。但是隔離性具體要隔離到什么程度,我們就有了隔離級別的概念?。ū确秸f我們去服裝店,如果是換褲子,我們會進試衣間,如果是換上衣,我們會根據(jù)情況,如果是換鞋子,那直接在外面換就可以了,所以這就是因為影響不同而導致隔離級別的不同)

三、隔離級別(讀寫并發(fā))
1、讀未提交【Read Uncommitted】: 在該隔離級別,所有的事務都可以看到其他事務沒有提交的執(zhí)行結果。 (實際生產(chǎn)中不可能使用這種隔離級別的),但是相當于沒有任何隔離性,也會有很多并發(fā)問題,如臟讀,幻讀,不可重復讀等,我們上面為了做實驗方便,用的就是這個隔離性。
2、讀提交【Read Committed】 :該隔離級別是大多數(shù)數(shù)據(jù)庫的默認的隔離級別(不是 MySQL 默認的)。它滿足了隔離的簡單定義:一個事務只能看到其他的已經(jīng)提交的事務所做的改變。(就是你提交了我還沒退出就能看到)這種隔離級別會引起不可重復讀, 即一個事務執(zhí)行時,如果多次 select, 可能得到不同的結果。
3、可重復讀【Repeatable Read】: 這是 MySQL 默認的隔離級別,它確保同一個事務,在執(zhí)行中,多次讀取操作數(shù)據(jù)時,會看到同樣的數(shù)據(jù)行。(就是你提交了,并且我結束了,才能看到 )但是會有幻讀問題。
4、串行化【Serializable】: 這是事務的最高隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決了幻讀的問題。(就是你結束了我才能執(zhí)行,保證數(shù)據(jù)絕對安全)它在每個讀的數(shù)據(jù)行上面加上共享鎖。但是可能會導致超時和鎖競爭(這種隔離級別太極端, 實際生產(chǎn)基本不使用)
隔離級別如何實現(xiàn):隔離,基本都是通過鎖實現(xiàn)的,不同的隔離級別,鎖的使用是不同的。常見有,表鎖,行鎖,讀鎖,寫鎖,間隙鎖(GAP),Next-Key鎖(GAP+行鎖)等。不過,我們目前現(xiàn)有這個認識就行,先關注上層使用。
上面四種隔離級別關注的場景都是當有一個人在進行寫的時候另一個人來讀(讀寫并發(fā))
四、查看與設置隔離性
-- 查看
mysql> SELECT @@global.tx_isolation; --查看全局隔級別
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --查看會話(當前)全局隔級別(默認用全局的初始化)
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --默認同上
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
--設置
-- 設置當前會話 or 全局隔離級別語法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED |
REPEATABLE READ | SERIALIZABLE}
--設置當前會話隔離性,另起一個會話,看不多,只影響當前會話
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation; --全局隔離性還是RR
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --會話隔離性成為串行化
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --同上
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
--設置全局隔離性,另起一個會話,會被影響
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
-- 注意,如果沒有現(xiàn)象,關閉mysql客戶端,重新連接。
五、讀未提交【Read Uncommitted】
--幾乎沒有加鎖,雖然效率高,但是問題太多,嚴重不建議采用
--終端A -- 設置隔離級別為 讀未提交 mysql> set global transaction isolation level read uncommitted; Query OK, 0 rows affected (0.00 sec) --重啟客戶端 mysql> select @@tx_isolation; +------------------+ | @@tx_isolation | +------------------+ | READ-UNCOMMITTED | +------------------+ 1 row in set, 1 warning (0.00 sec) mysql> select * from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> begin; --開啟事務 Query OK, 0 rows affected (0.00 sec) mysql> update account set blance=123.0 where id=1; --更新指定行 Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 --沒有commit哦?。?! --終端B mysql> begin; mysql> select * from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 123.00 | --讀到終端A更新但是未commit的數(shù)據(jù)[insert,delete同樣] | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec)
一個事務在執(zhí)行中,讀到另一個執(zhí)行中事務的更新(或其他操作)但是未commit的數(shù)據(jù),這種現(xiàn)象叫做臟讀(dirty read)
六、讀提交(不可重復讀)【Read Committed】
-- 終端A mysql> set global transaction isolation level read committed; Query OK, 0 rows affected (0.00 sec) --重啟客戶端 mysql> select * from account; --查看當前數(shù)據(jù) +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 123.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> begin; --手動開啟事務,同步的開始終端B事務 Query OK, 0 rows affected (0.00 sec) mysql> update account set blance=321.0 where id=1; --更新張三數(shù)據(jù) Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 --切換終端到終端B,查看數(shù)據(jù)。 mysql> commit; --commit提交! Query OK, 0 rows affected (0.01 sec) --切換終端到終端B,再次查看數(shù)據(jù)。 --終端B mysql> begin; --手動開啟事務,和終端A一前一后 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --終端A commit之前,查看不到 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 123.00 | --老的值 | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) --終端A commit之后,看到了! --but,此時還在當前事務中,并未commit,那么就造成了,同一個事務內,同樣的讀取,在不同的時間段(依舊還在事務操作中!),讀取到了不同的值,這種現(xiàn)象叫做不可重復讀(non reapeatable read)!!(這個是問題嗎??) mysql> select *from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 321.00 | --新的值 | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec)
問題:不可重復讀是問題嗎??會造成什么后果??
比方說小王要負責給tom把工資從3200改成4500.而小張要負責根據(jù)工資表篩選獎品發(fā)放名單,在讀提交的情景下,當小張運行到中間的時候突然小王把修改的信息提交了,這個時候小張就有可能在員工表里看到兩個tom,這就會造成工作上的失誤??!

所以我們不應該讓我們的用戶在實際操作的時候,兩個正在執(zhí)行的事物一方能夠讀到另一方的提交。所以雖然讀提交比讀未提交隔離級別高,但是我們也不推薦?。∫驗闀е律鲜龅牟豢芍貜妥x的問題??!
七、可重復讀【Repeatable Read】
mysql> select * from account; --終端A中事務 commit之前,查看當前表中數(shù)據(jù),數(shù)據(jù)未更新 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> select * from account; --終端A中事務 commit 之后,查看當前表中數(shù)據(jù),數(shù)據(jù)未更新 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) --可以看到,在終端B中,事務無論什么時候進行查找,看到的結果都是一致的,這叫做可重復讀! mysql> commit; --結束事務 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --再次查看,看到最新的更新數(shù)據(jù) +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec)
--如果將上面的終端A中的update操作,改成insert操作,會有什么問題??
--終端A mysql> select *from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> begin; --開啟事務,終端B同步開啟 Query OK, 0 rows affected (0.00 sec) mysql> insert into account (id,name,blance) values(3, '王五', 5432.0); Query OK, 1 row affected (0.00 sec) --切換到終端B,查看另一個事務是否能看到 mysql> commit; --提交事務 Query OK, 0 rows affected (0.00 sec) --切換終端到終端B,查看數(shù)據(jù)。 mysql> select * from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | +----+--------+----------+ 3 rows in set (0.00 sec) --終端B mysql> begin; --開啟事務 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --終端A commit前 查看 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> select * from account; --終端A commit后 查看 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec)
--多次查看,發(fā)現(xiàn)終端A在對應事務中insert的數(shù)據(jù),在終端B的事務周期中,也沒有什么影響,也符合可重復的特點。
但是,一般的數(shù)據(jù)庫在可重復讀情況的時候,無法屏蔽其他事務insert的數(shù)據(jù)(為什么?因為隔離性實現(xiàn)是對數(shù)據(jù)加鎖完成的,而insert待插入的數(shù)據(jù)因為并不存在,那么一般加鎖無法屏蔽這類問題),會造成雖然大部分內容是可重復讀的,但是insert的數(shù)據(jù)在可重復讀情況被讀取出來,導致多次查找時,會多查找出來新的記錄,就如同產(chǎn)生了幻覺。這種現(xiàn)象,叫做幻讀(phantom read)。
很明顯,MySQL在RR級別的時候,是解決了幻讀問題的(解決的方式是用Next-Key鎖(GAP+行鎖)解決的。這塊比較難,可了解一下)。
mysql> select * from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> commit; --結束事務 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --看到更新 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | +----+--------+----------+ 3 rows in set (0.00 sec)
八、串行化【serializable】
--對所有操作全部加鎖,進行串行化,不會有問題,但是只要串行化,效率很低,幾乎完全不會被采用
--終端A mysql> set global transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | SERIALIZABLE | +----------------+ 1 row in set, 1 warning (0.00 sec) mysql> begin; --開啟事務,終端B同步開啟 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --兩個讀取不會串行化,共享鎖 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | +----+--------+----------+ 3 rows in set (0.00 sec) mysql> update account set blance=1.00 where id=1; --終端A中有更新或者其他操作,會阻塞。直到終端B事務 提交。 Query OK, 1 row affected (18.19 sec) Rows matched: 1 Changed: 1 Warnings: 0 --終端B mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --兩個讀取不會串行化 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | +----+--------+----------+ 3 rows in set (0.00 sec) mysql> commit; --提交之后,終端A中的update才會提交。 Query OK, 0 rows affected (0.00 sec)

九、總結
其中隔離級別越嚴格,安全性越高,但數(shù)據(jù)庫的并發(fā)性能也就越低,往往需要在兩者之間找一個平衡點。(這個平衡點不是mysql決定而是由場景和需求決定的,所以mysql不是為我們指定隔離方式,而是給我們不同的隔離方案讓我們用戶根據(jù)實際場景需求做選擇)
- 臟讀(dirty read) 的重點是CUD:一個事務在執(zhí)行中,讀到另一個執(zhí)行中事務的更新(或其他操作)但是未commit的數(shù)據(jù)
- 不可重復讀的重點是U和D:同樣的條件, 你讀取過的數(shù)據(jù),再次讀取出來發(fā)現(xiàn)值不一樣了
- 幻讀的重點在于C:同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣
說明: mysql 默認的隔離級別是可重復讀,一般情況下不要修改
上面的例子可以看出,事務也有長短事務這樣的概念。事務間互相影響,指的是事務在并行執(zhí)行的時候,即都沒有commit的時候,影響會比較大
十、一致性(Consistency)
事務執(zhí)行的結果,必須使數(shù)據(jù)庫從一個一致性狀態(tài),變到另一個一致性狀態(tài)。當數(shù)據(jù)庫只包含事務成功提交的結果時,數(shù)據(jù)庫處于一致性狀態(tài)。如果系統(tǒng)運行發(fā)生中斷,某個事務尚未完成而被迫中斷,而改未完成的事務 對數(shù)據(jù)庫所做的修改已被寫入數(shù)據(jù)庫,此時數(shù)據(jù)庫就處于一種不正確(不一致)的狀態(tài)。因此一致性是通過原子性來保證的。
其實一致性并非技術層面的概念,而是和用戶的業(yè)務邏輯強相關,一般MySQL提供技術支持,但是一致性還是要用戶業(yè)務邏輯做支撐,也就是,一致性,是由用戶決定的。(舉個例子,比方說有個菜鳥程序員,對轉賬操作只在原賬戶減了錢,但是沒有在新賬戶加錢所以你用戶至少得保證你寫的mysql語句是對的、是完整的、業(yè)務邏輯是對的?。?!你能做到這些,那我就能保證你有一致性。但是你要是本身就有問題,我mysql又不了解業(yè)務,出錯了關我什么事呢?所以一致性應該由mysql和程序員共同完成,這才是對一致性正確的理解)
而技術上,通過A(原子性)I(隔離性)D(持久性)保證C(一致性) ,前者是因,后者是果。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
MySQL的執(zhí)行原理之 MySQL的查詢重寫規(guī)則詳解
MySQL 性能調優(yōu)是一個復雜且多維度的過程,下面從數(shù)據(jù)庫設計、查詢優(yōu)化、配置參數(shù)調整、硬件優(yōu)化幾個方面為你介紹相關的調優(yōu)方法,本文給大家介紹MySQL的執(zhí)行原理之 MySQL的查詢重寫規(guī)則,感興趣的朋友一起看看吧2025-04-04
MySQL JSON 查詢中的對象與數(shù)組技巧及查詢示例
MySQL中JSON對象和JSON 數(shù)組查詢的詳細介紹及帶有WHERE條件的查詢示例,本文給大家介紹的非常詳細,mysql json查詢示例相關知識感興趣的朋友一起看看吧2025-06-06
MySQL索引查詢limit?offset及排序order?by用法
這篇文章主要介紹了MySQL限制數(shù)據(jù)返回條數(shù)limit?offset及排序order?by用法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
MySql中流程控制函數(shù)/統(tǒng)計函數(shù)/分組查詢用法解析
這篇文章主要介紹了MySql中流程控制函數(shù)/統(tǒng)計函數(shù)/分組查詢用法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07
mysql中l(wèi)ower_case_table_names=1參數(shù)的作用
lower_case_table_names是MySQL數(shù)據(jù)庫中的一個系統(tǒng)變量,本文主要介紹了mysql中l(wèi)ower_case_table_names=1參數(shù)的作用,具有一定的參考價值,感興趣的可以了解一下2024-08-08

