一文快速搞懂MySQLInnoDB事務(wù)ACID實(shí)現(xiàn)原理

【51CTO.com原創(chuàng)稿件】說(shuō)到數(shù)據(jù)庫(kù)事務(wù),想到的就是要么都做修改,要么都不做,或者是 ACID 的概念。其實(shí)事務(wù)的本質(zhì)就是鎖、并發(fā)和重做日志的結(jié)合體。

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都做網(wǎng)站、網(wǎng)站建設(shè)、安寧網(wǎng)絡(luò)推廣、小程序制作、安寧網(wǎng)絡(luò)營(yíng)銷、安寧企業(yè)策劃、安寧品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供安寧建站搭建服務(wù),24小時(shí)服務(wù)熱線:18980820575,官方網(wǎng)址:bm7419.com

這一篇主要講一下 InnoDB 中的事務(wù)到底是如何實(shí)現(xiàn) ACID 的:

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔離性(isolation)
  • 持久性(durability)
    隔離性
    隔離性的實(shí)現(xiàn)原理就是鎖,因而隔離性也可以稱為并發(fā)控制、鎖等。事務(wù)的隔離性要求每個(gè)讀寫事務(wù)的對(duì)象對(duì)其他事務(wù)的操作對(duì)象能互相分離。
    再者,比如操作緩沖池中的 LRU 列表,刪除,添加、移動(dòng) LRU 列表中的元素,為了保證一致性那么就要鎖的介入。
    InnoDB 使用鎖為了支持對(duì)共享資源進(jìn)行并發(fā)訪問(wèn),提供數(shù)據(jù)的完整性和一致性。
    那么到底 InnoDB 支持什么樣的鎖呢?我們先來(lái)看下 InnoDB 的鎖的介紹:
    InnoDB 中的鎖
    你可能聽(tīng)過(guò)各種各樣的 InnoDB 的數(shù)據(jù)庫(kù)鎖,Gap 鎖,共享鎖,排它鎖,讀鎖,寫鎖等等。但是 InnoDB 的標(biāo)準(zhǔn)實(shí)現(xiàn)的鎖只有 2 類,一種是行級(jí)鎖,一種是意向鎖。
    InnoDB 實(shí)現(xiàn)了如下兩種標(biāo)準(zhǔn)的行級(jí)鎖:
  • 共享鎖(讀鎖 S Lock),允許事務(wù)讀一行數(shù)據(jù)。
  • 排它鎖(寫鎖 X Lock),允許事務(wù)刪除一行數(shù)據(jù)或者更新一行數(shù)據(jù)。
    行級(jí)鎖中,除了 S 和 S 兼容,其他都不兼容。
    InnoDB 支持兩種意向鎖(即為表級(jí)別的鎖):
  • 意向共享鎖(讀鎖 IS Lock),事務(wù)想要獲取一張表的幾行數(shù)據(jù)的共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的 IS 鎖。
  • 意向排他鎖(寫鎖 IX Lock),事務(wù)想要獲取一張表中幾行數(shù)據(jù)的排它鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排它鎖前必須先取得該表的 IX 鎖。
    首先解釋一下意向鎖,以下為意向鎖的意圖解釋:
    The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.
    大致意思是加意向鎖為了表明某個(gè)事務(wù)正在鎖定一行或者將要鎖定一行數(shù)據(jù)。
    首先申請(qǐng)意向鎖的動(dòng)作是 InnoDB 完成的,怎么理解意向鎖呢?例如:事務(wù) A 要對(duì)一行記錄 R 進(jìn)行上 X 鎖,那么 InnoDB 會(huì)先申請(qǐng)表的 IX 鎖,再鎖定記錄 R 的 X 鎖。
    在事務(wù) A 完成之前,事務(wù) B 想要來(lái)個(gè)全表操作,此時(shí)直接在表級(jí)別的 IX 就告訴事務(wù) B 需要等待而不需要在表上判斷每一行是否有鎖。
    意向排它鎖存在的價(jià)值在于節(jié)約 InnoDB 對(duì)于鎖的定位和處理性能。另外注意了,除了全表掃描以外意向鎖都不會(huì)阻塞。
    鎖的算法
    InnoDB 有 3 種行鎖的算法:
  • Record Lock:?jiǎn)蝹€(gè)行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個(gè)范圍,而非記錄本身。
  • Next-Key Lock:結(jié)合 Gap Lock 和 Record Lock,鎖定一個(gè)范圍,并且鎖定記錄本身。主要解決的問(wèn)題是 RR 隔離級(jí)別下的幻讀。
    這里主要講一下 Next-Key Lock。MySQL 默認(rèn)隔離級(jí)別 RR 下,這時(shí)默認(rèn)采用 Next-Key locks。
    這種間隙鎖的目的就是為了阻止多個(gè)事務(wù)將記錄插入到同一范圍內(nèi)從而導(dǎo)致幻讀。注意了,如果走唯一索引,那么 Next-Key Lock 會(huì)降級(jí)為 Record Lock。
    前置條件為事務(wù)隔離級(jí)別為 RR 且 SQL 走的非唯一索引、主鍵索引。如果不是則根本不會(huì)有 Gap 鎖!先舉個(gè)例子來(lái)講一下 Next-Key Lock。
    首先建立一張表:
    mysql> show create table m_test_db.M; 
    +-------+----------------------------------------------------------+ 
    | Table | Create Table                                                                                                                                                                                                                                     | 
    +-------+----------------------------------------------------------+ 
    | M     | CREATE TABLE `M` ( 
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `user_id` varchar(45) DEFAULT NULL, 
    `name` varchar(45) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `IDX_USER_ID` (`user_id`) 
    ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 | 
    +-------+----------------------------------------------------------+ 
    1 row in set (0.00 sec) 

    首先 Session A 去拿到 user_id 為 26 的 X 鎖,用 force index,強(qiáng)制走這個(gè)非唯一輔助索引,因?yàn)檫@張表里的數(shù)據(jù)很少。

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

mysql> select * from m_test_db.M force index(IDX_USER_ID) where user_id = '26' for update;
+----+---------+-------+
| id | user_id | name |
+----+---------+-------+
| 5 | 26 | jerry |
| 6 | 26 | ketty |
+----+---------+-------+

`2 rows in set (0.00 sec) `
然后 Session B 插入數(shù)據(jù):

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

mysql> insert into m_test_db.M values (8,25,'GrimMjx');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


明明插入的數(shù)據(jù)和鎖住的數(shù)據(jù)沒(méi)有毛線關(guān)系,為什么還會(huì)阻塞等鎖最后超時(shí)呢?這就是 Next-Key Lock 實(shí)現(xiàn)的。
畫張圖你就明白了:![](https://s1.51cto.com/images/blog/201904/03/ea21d2246fdb9b22d050882a0d885d2a.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

Gap 鎖鎖住的位置,不是記錄本身,而是兩條記錄之間的間隔 Gap,其實(shí)就是防止幻讀(同一事務(wù)下,連續(xù)執(zhí)行兩句同樣的 SQL 得到不同的結(jié)果)。
為了保證圖上 3 個(gè)小箭頭中間不會(huì)插入滿足條件的新記錄,所以用到了 Gap 鎖防止幻讀。
簡(jiǎn)單的 Insert 會(huì)在 Insert 的行對(duì)應(yīng)的索引記錄上加一個(gè) Record Lock 鎖,并沒(méi)有 Gap 鎖,所以并不會(huì)阻塞其他 Session 在 Gap 間隙里插入記錄。
不過(guò)在 Insert 操作之前,還會(huì)加一種鎖,官方文檔稱它為 Intention Gap Lock,也就是意向的 Gap 鎖。
這個(gè)意向 Gap 鎖的作用就是預(yù)示著當(dāng)多事務(wù)并發(fā)插入相同的 Gap 空隙時(shí),只要插入的記錄不是 Gap 間隙中的相同位置,則無(wú)需等待其他 Session 就可完成,這樣就使得 Insert 操作無(wú)須加真正的 Gap Lock。
Session A 插入數(shù)據(jù):
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> insert into m_test_db.M values (10,25,'GrimMjx');Query OK, 1 row affected (0.00 sec)
Session B 插入數(shù)據(jù),完全沒(méi)有問(wèn)題,沒(méi)有阻塞:
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> insert into m_test_db.M values (11,27,'Mjx');Query OK, 1 row affected (0.00 sec)
**死鎖**
了解了 InnoDB 是如何加鎖的,現(xiàn)在可以去嘗試分析死鎖。死鎖的本質(zhì)就是兩個(gè)事務(wù)相互等待對(duì)方釋放持有的鎖導(dǎo)致的,關(guān)鍵在于不同 Session 加鎖的順序不一致。
不懂死鎖概念模型的可以先看一幅圖:![](https://s1.51cto.com/images/blog/201904/03/e18abb279d6c1c7494a70460f0ba8c52.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

左鳥(niǎo)線程獲取了左肉的鎖,想要獲取右肉的鎖,右鳥(niǎo)的線程獲取了右肉的鎖。
右鳥(niǎo)想要獲取左肉的鎖。左鳥(niǎo)沒(méi)有釋放左肉的鎖,右鳥(niǎo)也沒(méi)有釋放右肉的鎖,那么這就是死鎖。
接下來(lái)還用剛才的那張 M 表來(lái)分析一下數(shù)據(jù)庫(kù)死鎖,比較好理解:![](https://s1.51cto.com/images/blog/201904/03/b9ba3e051a8796966437328ac5cd9bd5.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

**四種隔離級(jí)別**
那么按照最嚴(yán)格到最松的順序來(lái)講一下四種隔離級(jí)別:
**①Serializable(可序列化)**
最高事務(wù)隔離級(jí)別。主要用在 InnoDB 存儲(chǔ)引擎的分布式事務(wù)。強(qiáng)制事務(wù)排序,串行化執(zhí)行事務(wù)。
不需要沖突控制,但是慢速設(shè)備。根據(jù) Jim Gray 在《Transaction Processing》一書中指出,Read Committed 和 Serializable 的開(kāi)銷幾乎是一樣的,甚至 Serializable 更優(yōu)。
Session A 設(shè)置隔離級(jí)別為 Serializable,并開(kāi)始事務(wù)執(zhí)行一句 SQL:
mysql> select @@tx_isolation; 
+----------------+ 
| @@tx_isolation | 
+----------------+ 
| SERIALIZABLE   | 
+----------------+ 
1 row in set, 1 warning (0.00 sec) 

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

mysql> select * from m_test_db.M; 
+----+---------+-------+ 
| id | user_id | name  | 
+----+---------+-------+ 
|  1 | 20      | mjx   | 
|  2 | 21      | ben   | 
|  3 | 23      | may   | 
|  4 | 24      | tom   | 
|  5 | 26      | jerry | 
|  6 | 26      | ketty | 
|  7 | 28      | kris  | 
+----+---------+-------+ 
7 rows in set (0.00 sec) 
Session Binsert 一條數(shù)據(jù),超時(shí):
mysql> start transaction; 
Query OK, 0 rows affected (0.00 sec) 

mysql> insert into m_test_db.M values (9,30,'test'); 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 
**②Repeatable Read(可重復(fù)讀)**
一個(gè)事務(wù)按相同的查詢條件讀取以前檢索過(guò)的數(shù)據(jù),其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),產(chǎn)生幻讀。
InnoDB 存儲(chǔ)引擎在 RR 隔離級(jí)別下,已經(jīng)使用 Next-Key Lock 算法避免了幻讀,了解概念即可。
InnoDB 使用 MVCC 來(lái)讀取數(shù)據(jù),RR 隔離級(jí)別下,總是讀取事務(wù)開(kāi)始時(shí)的行數(shù)據(jù)版本。
Session A 查看 id=1 的數(shù)據(jù):
mysql> set tx_isolation='repeatable-read'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

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

mysql> select * from m_test_db.M where id =1; 
+----+---------+---------+ 
| id | user_id | name    | 
+----+---------+---------+ 
|  1 | 20      | GrimMjx | 
+----+---------+---------+ 
1 row in set (0.01 sec) 
Session B 修改 id=1 的數(shù)據(jù):
mysql> set tx_isolation='repeatable-read'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

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

mysql> update m_test_db.M set name = 'Mjx'; 
Query OK, 7 rows affected (0.00 sec) 
Rows matched: 7  Changed: 7  Warnings: 0 
然后現(xiàn)在 Session A 再查看一下 id=1 的數(shù)據(jù),數(shù)據(jù)還是事務(wù)開(kāi)始時(shí)候的數(shù)據(jù)。
mysql> select * from m_test_db.M where id =1; 
+----+---------+---------+ 
| id | user_id | name    | 
+----+---------+---------+ 
|  1 | 20      | GrimMjx | 
+----+---------+---------+ 
1 row in set (0.00 sec) 
**③Read Committed(讀已提交)**
事務(wù)從開(kāi)始直到提交之前,所做的任何修改對(duì)其他事務(wù)都是不可見(jiàn)的。
InnoDB 使用 MVCC 來(lái)讀取數(shù)據(jù),RC 隔離級(jí)別下,總是讀取被鎖定行最新的快照數(shù)據(jù)。
Session A 查看 id=1 的數(shù)據(jù):
mysql> set tx_isolation='read-committed'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

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

mysql> select * from m_test_db.M where id =1; 
+----+---------+------+ 
| id | user_id | name | 
+----+---------+------+ 
|  1 | 20      | Mjx  | 
+----+---------+------+ 
1 row in set (0.00 sec) 
Session B 修改 id=1 的 Name 并且 Commit:
mysql> set tx_isolation='repeatable-read'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

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

mysql> update m_test_db.M set name = 'testM' where id =1; 
Query OK, 1 row affected (0.00 sec) 
Rows matched: 1  Changed: 1  Warnings: 0 

// 注意,這里commit了! 
mysql> commit; 
Query OK, 0 rows affected (0.00 sec) 
Session A 再查詢 id=1 的記錄,發(fā)現(xiàn)數(shù)據(jù)已經(jīng)是最新的數(shù)據(jù):
mysql> select * from m_test_db.M where id =1; 
+----+---------+-------+ 
| id | user_id | name  | 
+----+---------+-------+ 
|  1 | 20      | testM | 
+----+---------+-------+ 
1 row in set (0.00 sec) 
**④Read Uncommitted(讀未提交)**
事務(wù)中的修改,即使沒(méi)有提交,對(duì)其他事務(wù)也都是可見(jiàn)的。
Session A 查看一下 id=3 的數(shù)據(jù),沒(méi)有 Commit:
mysql> set tx_isolation='read-uncommitted'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

mysql> select @@tx_isolation; 
+------------------+ 
| @@tx_isolation   | 
+------------------+ 
| READ-UNCOMMITTED | 
+------------------+ 
1 row in set, 1 warning (0.00 sec) 

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

mysql> select * from m_test_db.M where id =3; 
+----+---------+------+ 
| id | user_id | name | 
+----+---------+------+ 
|  3 | 23      | may  | 
+----+---------+------+ 
1 row in set (0.00 sec) 
Session B 修改 id=3 的數(shù)據(jù),但是沒(méi)有 Commit:
mysql> set tx_isolation='read-uncommitted'; 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

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

mysql> update m_test_db.M set name = 'GRIMMJX' where id = 3; 
Query OK, 1 row affected (0.00 sec) 
Rows matched: 1  Changed: 1  Warnings: 0 
Session A 再次查看則看到了新的結(jié)果:
mysql> select * from m_test_db.M where id =3; 
+----+---------+---------+ 
| id | user_id | name    | 
+----+---------+---------+ 
|  3 | 23      | GRIMMJX | 
+----+---------+---------+ 
1 row in set (0.00 sec) 
這里花了很多筆墨來(lái)介紹隔離性,這是比較重要,需要靜下心來(lái)學(xué)習(xí)的特性。所以也是放在第一個(gè)的原因。
**原子性、一致性、持久性**
事務(wù)隔離性由鎖實(shí)現(xiàn),原子性、一致性和持久性由數(shù)據(jù)庫(kù)的 redo log 和 undo log 實(shí)現(xiàn)。
redo log 稱為重做日志,用來(lái)保證事務(wù)的原子性和持久性,恢復(fù)提交事務(wù)修改的頁(yè)操作。
undo log 來(lái)保證事務(wù)的一致性,undo 回滾行記錄到某個(gè)特性版本及 MVCC 功能。兩者內(nèi)容不同。redo 記錄物理日志,undo 是邏輯日志。
**redo**
重做日志由重做日志緩沖(redo log buffer)和重做日志文件(redo log file)組成,前者是易失的,后者是持久的。
InnoDB 通過(guò) Force Log at Commit 機(jī)制來(lái)實(shí)現(xiàn)持久性,當(dāng) Commit 時(shí),必須先將事務(wù)的所有日志寫到重做日志文件進(jìn)行持久化,待 Commit 操作完成才算完成。
當(dāng)事務(wù)提交時(shí),日志不寫入重做日志文件,而是等待一個(gè)事件周期后再執(zhí)行 Fsync 操作,由于并非強(qiáng)制在事務(wù)提交時(shí)進(jìn)行一次 Fsync 操作,顯然這可以提高數(shù)據(jù)庫(kù)性能。
請(qǐng)記住 3 點(diǎn):
重做日志是在 InnoDB 層產(chǎn)生的。
重做日志是物理格式日志,記錄的是對(duì)每個(gè)頁(yè)的修改。
重做日志在事務(wù)進(jìn)行中不斷被寫入。
**undo**
事務(wù)回滾和 MVCC,這就需要 undo。undo 是邏輯日志,只是將數(shù)據(jù)庫(kù)邏輯恢復(fù)到原來(lái)的樣子,但是數(shù)據(jù)結(jié)構(gòu)和頁(yè)本身在回滾之后可能不同。
例如:用戶執(zhí)行 insert 10w 條數(shù)據(jù)的事務(wù),表空間因而增大。用戶執(zhí)行 ROLLBACK 之后,會(huì)對(duì)插入的數(shù)據(jù)回滾,但是表空間大小不會(huì)因此收縮。
實(shí)際的做法就是做與之前想法的操作,Insert 對(duì)應(yīng) Delete,Update 對(duì)應(yīng)反向 Update 來(lái)實(shí)現(xiàn)原子性。
InnoDB 中 MVCC 的實(shí)現(xiàn)就是靠 undo,舉個(gè)經(jīng)典的例子:Bob 給 Smith 轉(zhuǎn) 100 元,那么就存在以下 3 個(gè)版本,RR 隔離級(jí)別下,對(duì)于快照數(shù)據(jù),總是讀事務(wù)開(kāi)始的行數(shù)據(jù)版本見(jiàn)黃標(biāo)。
RC 隔離級(jí)別下,對(duì)于快照數(shù)據(jù),總是讀最新的一份快照數(shù)據(jù)見(jiàn)紅標(biāo):
![](https://s1.51cto.com/images/blog/201904/03/abed48da5c98aad28c72bb229d277d43.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
undo log 會(huì)產(chǎn)生 redo log,因?yàn)?undo log 需要持久性保護(hù) 。
最后,你會(huì)發(fā)現(xiàn)姜承堯的 MySQL InnoDB 書上的很多內(nèi)容都是官方手冊(cè)的翻譯,無(wú)論是看源碼還是學(xué)習(xí)新框架,最好看原汁原味的。
只要你堅(jiān)持,一步一步來(lái),總歸會(huì)成功的。切忌,學(xué)技術(shù)急不來(lái),快就是穩(wěn),穩(wěn)就是快。

新聞名稱:一文快速搞懂MySQLInnoDB事務(wù)ACID實(shí)現(xiàn)原理
瀏覽地址:http://bm7419.com/article6/isgiig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、定制網(wǎng)站、定制開(kāi)發(fā)、網(wǎng)站設(shè)計(jì)公司、建站公司、軟件開(kāi)發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名