mysql怎么实现事务序列化_一文快速搞懂MySQL InnoDB事务ACID实现原理(转)
這一篇主要講一下 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ā)訪問,提供數(shù)據(jù)的完整性和一致性。
那么到底 InnoDB 支持什么樣的鎖呢?我們先來看下 InnoDB 的鎖的介紹:
InnoDB 中的鎖
你可能聽過各種各樣的 InnoDB 的數(shù)據(jù)庫鎖,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 想要來個(gè)全表操作,此時(shí)直接在表級(jí)別的 IX 就告訴事務(wù) B 需要等待而不需要在表上判斷每一行是否有鎖。
意向排它鎖存在的價(jià)值在于節(jié)約 InnoDB 對(duì)于鎖的定位和處理性能。另外注意了,除了全表掃描以外意向鎖都不會(huì)阻塞。
鎖的算法
InnoDB 有 3 種行鎖的算法:
Record Lock:單個(gè)行記錄上的鎖。
Gap Lock:間隙鎖,鎖定一個(gè)范圍,而非記錄本身。
Next-Key Lock:結(jié)合 Gap Lock 和 Record Lock,鎖定一個(gè)范圍,并且鎖定記錄本身。主要解決的問題是 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è)例子來講一下 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.00sec)
mysql> select * from m_test_db.M force index(IDX_USER_ID) where user_id = '26' forupdate;+----+---------+-------+
| 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.00sec)
mysql> insert into m_test_db.M values (8,25,'GrimMjx');
ERROR1205 (HY000): Lock wait timeout exceeded; try restarting transaction
明明插入的數(shù)據(jù)和鎖住的數(shù)據(jù)沒有毛線關(guān)系,為什么還會(huì)阻塞等鎖最后超時(shí)呢?這就是 Next-Key Lock 實(shí)現(xiàn)的。
畫張圖你就明白了:
Gap 鎖鎖住的位置,不是記錄本身,而是兩條記錄之間的間隔 Gap,其實(shí)就是防止幻讀(同一事務(wù)下,連續(xù)執(zhí)行兩句同樣的 SQL 得到不同的結(jié)果)。
為了保證圖上 3 個(gè)小箭頭中間不會(huì)插入滿足條件的新記錄,所以用到了 Gap 鎖防止幻讀。
簡(jiǎn)單的 Insert 會(huì)在 Insert 的行對(duì)應(yīng)的索引記錄上加一個(gè) Record Lock 鎖,并沒有 Gap 鎖,所以并不會(huì)阻塞其他 Session 在 Gap 間隙里插入記錄。
不過在 Insert 操作之前,還會(huì)加一種鎖,官方文檔稱它為 Intention Gap Lock,也就是意向的 Gap 鎖。
這個(gè)意向 Gap 鎖的作用就是預(yù)示著當(dāng)多事務(wù)并發(fā)插入相同的 Gap 空隙時(shí),只要插入的記錄不是 Gap 間隙中的相同位置,則無需等待其他 Session 就可完成,這樣就使得 Insert 操作無須加真正的 Gap Lock。
Session A 插入數(shù)據(jù):
mysql>begin;
Query OK,0 rows affected (0.00sec)
mysql> insert into m_test_db.M values (10,25,'GrimMjx');
Query OK,1 row affected (0.00 sec)
Session B 插入數(shù)據(jù),完全沒有問題,沒有阻塞:
mysql>begin;
Query OK,0 rows affected (0.00sec)
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 加鎖的順序不一致。
不懂死鎖概念模型的可以先看一幅圖:
左鳥線程獲取了左肉的鎖,想要獲取右肉的鎖,右鳥的線程獲取了右肉的鎖。
右鳥想要獲取左肉的鎖。左鳥沒有釋放左肉的鎖,右鳥也沒有釋放右肉的鎖,那么這就是死鎖。
接下來還用剛才的那張 M 表來分析一下數(shù)據(jù)庫死鎖,比較好理解:
四種隔離級(jí)別
那么按照最嚴(yán)格到最松的順序來講一下四種隔離級(jí)別:
①Serializable(可序列化)
最高事務(wù)隔離級(jí)別。主要用在 InnoDB 存儲(chǔ)引擎的分布式事務(wù)。強(qiáng)制事務(wù)排序,串行化執(zhí)行事務(wù)。
不需要沖突控制,但是慢速設(shè)備。根據(jù) Jim Gray 在《Transaction Processing》一書中指出,Read Committed 和 Serializable 的開銷幾乎是一樣的,甚至 Serializable 更優(yōu)。
Session A 設(shè)置隔離級(jí)別為 Serializable,并開始事務(wù)執(zhí)行一句 SQL
mysql> select@@tx_isolation;+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00sec)
mysql>start transaction;
Query OK,0 rows affected (0.00sec)
mysql> select * fromm_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 B insert 一條數(shù)據(jù),超時(shí):
mysql>start transaction;
Query OK,0 rows affected (0.00sec)
mysql> insert into m_test_db.M values (9,30,'test');
ERROR1205 (HY000): Lock wait timeout exceeded; try restarting transaction
②Repeatable Read(可重復(fù)讀)(mysql默認(rèn)隔離級(jí)別)
一個(gè)事務(wù)按相同的查詢條件讀取以前檢索過的數(shù)據(jù),其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),產(chǎn)生幻讀。
InnoDB 存儲(chǔ)引擎在 RR 隔離級(jí)別下,已經(jīng)使用 Next-Key Lock 算法避免了幻讀,了解概念即可。
InnoDB 使用 MVCC 來讀取數(shù)據(jù),RR 隔離級(jí)別下,總是讀取事務(wù)開始時(shí)的行數(shù)據(jù)版本。
Session A 查看 id=1 的數(shù)據(jù):
mysql> set tx_isolation='repeatable-read';
Query OK,0 rows affected, 1 warning (0.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
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.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
mysql> update m_test_db.M set name = 'Mjx';
Query OK,7 rows affected (0.00sec)
Rows matched:7 Changed: 7 Warnings: 0
然后現(xiàn)在 Session A 再查看一下 id=1 的數(shù)據(jù),數(shù)據(jù)還是事務(wù)開始時(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ù)從開始直到提交之前,所做的任何修改對(duì)其他事務(wù)都是不可見的。
InnoDB 使用 MVCC 來讀取數(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.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
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.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
mysql> update m_test_db.M set name = 'testM' where id =1;
Query OK,1 row affected (0.00sec)
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ù)中的修改,即使沒有提交,對(duì)其他事務(wù)也都是可見的。
Session A 查看一下 id=3 的數(shù)據(jù),沒有 Commit:
mysql> set tx_isolation='read-uncommitted';
Query OK,0 rows affected, 1 warning (0.00sec)
mysql> select@@tx_isolation;+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
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ù),但是沒有 Commit:
mysql> set tx_isolation='read-uncommitted';
Query OK,0 rows affected, 1 warning (0.00sec)
mysql>begin;
Query OK,0 rows affected (0.00sec)
mysql> update m_test_db.M set name = 'GRIMMJX' where id = 3;
Query OK,1 row affected (0.00sec)
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)
這里花了很多筆墨來介紹隔離性,這是比較重要,需要靜下心來學(xué)習(xí)的特性。所以也是放在第一個(gè)的原因。
原子性、一致性、持久性
事務(wù)隔離性由鎖實(shí)現(xiàn),原子性、一致性和持久性由數(shù)據(jù)庫的 redo log 和 undo log 實(shí)現(xiàn)。
redo log 稱為重做日志,用來保證事務(wù)的原子性和持久性,恢復(fù)提交事務(wù)修改的頁操作。
undo log 來保證事務(wù)的一致性,undo 回滾行記錄到某個(gè)特性版本及 MVCC 功能。兩者內(nèi)容不同。redo 記錄物理日志,undo 是邏輯日志。
redo
重做日志由重做日志緩沖(redo log buffer)和重做日志文件(redo log file)組成,前者是易失的,后者是持久的。
InnoDB 通過 Force Log at Commit 機(jī)制來實(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ù)庫性能。
請(qǐng)記住 3 點(diǎn):
重做日志是在 InnoDB 層產(chǎn)生的。
重做日志是物理格式日志,記錄的是對(duì)每個(gè)頁的修改。
重做日志在事務(wù)進(jìn)行中不斷被寫入。
undo
事務(wù)回滾和 MVCC,這就需要 undo。undo 是邏輯日志,只是將數(shù)據(jù)庫邏輯恢復(fù)到原來的樣子,但是數(shù)據(jù)結(jié)構(gòu)和頁本身在回滾之后可能不同。
例如:用戶執(zhí)行 insert 10w 條數(shù)據(jù)的事務(wù),表空間因而增大。用戶執(zhí)行 ROLLBACK 之后,會(huì)對(duì)插入的數(shù)據(jù)回滾,但是表空間大小不會(huì)因此收縮。
實(shí)際的做法就是做與之前想法的操作,Insert 對(duì)應(yīng) Delete,Update 對(duì)應(yīng)反向 Update 來實(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ù)開始的行數(shù)據(jù)版本-見黃標(biāo)。
RC 隔離級(jí)別下,對(duì)于快照數(shù)據(jù),總是讀最新的一份快照數(shù)據(jù)-見紅標(biāo):
undo log 會(huì)產(chǎn)生 redo log,因?yàn)?undo log 需要持久性保護(hù)?。
轉(zhuǎn)自:https://mp.weixin.qq.com/s/j7rf0kE9-Z80OsUJymoPBw
總結(jié)
以上是生活随笔為你收集整理的mysql怎么实现事务序列化_一文快速搞懂MySQL InnoDB事务ACID实现原理(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 字段值为 a b c_数据的
- 下一篇: mysql my-small.ini_M