Mysql 事務原理簡單分析

Mysql Innodb中的事務隔離級別

隔離級別 髒讀(Dirty Read) 不可重複讀(NonRepeatable Read) 幻讀(Phantom Read)
未提交讀(Read uncommitted) 可能 可能 可能
已提交讀(Read committed) 不可能 可能 可能
可重複讀(Repeatable read) 不可能 不可能 Innodb中不可能,後面解釋
可串行化(Serializable ) 不可能 不可能 不可能

髒讀

不可重複讀

幻讀

不可重複讀和幻讀的區別

這兩者有些相似。但不可重複讀重點在於update和delete,而幻讀的重點在於insert。如果使用鎖機制來實現這兩種隔離級別,在可重複讀中,該sql第一次讀取到數據後,就將這些數據加鎖,其它事務無法修改這些數據,就可以實現可重複讀了。但這種方法卻無法鎖住insert的數據,所以當事務A先前讀取了數據,或者修改了全部數據,事務B還是可以insert數據提交,這時事務A就會發現莫名其妙多了一條之前沒有的數據,這就是幻讀,不能通過行鎖來避免。 剛纔提到鎖機制,那麼我們最常見的就是悲觀鎖和樂觀鎖。

悲觀鎖和樂觀鎖

悲觀鎖

爲了保證事務的隔離性,就需要一致性鎖定讀,就是每次操作數據時都去鎖住數據,不管哪種操作(增、刪、改、查)都加鎖,以至於其他事務操作這些數據

樂觀鎖

一般都是給表新增version字段,然後先通過查詢到該數據的version版本,之後數據修改時都將vsersion字段當作where條件去操作數據,並且將version字段修改成version+1,若修改行數大於1表示修改成功,反之則修改失敗。

鎖的原理

不使用索引

CREATE TABLE `test1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

BEGIN;
-- 手動開啓一個事務,並在id = 1這條數據上加上排它鎖
SELECT * from test1 WHERE id = 1 for UPDATE;

BEGIN;
-- 手動開啓另外一個事務,此時給id=2的這條數據進行加排它鎖,結果會如何?
SELECT * from test1 WHERE id = 2 for UPDATE

發現此時居然查詢id=2的數據事務被卡住了。這是爲什麼呢?當表沒有創建索引時或者查詢語句沒有命中索引時,鎖住的是整個表的數據,因爲沒有命中索引故其會去掃描全表數據。 當一張表沒有索引時,innoDB會創建一個隱藏主鍵索引,當通過隱藏的主鍵索引去檢索時,將該表中所有的隱藏索引檢索一遍 例子:如果手動開始事務,並在id=1的數據上手動加上排它鎖.如果此時再去查詢id=2的數據時,發現此語句卡住了。 故得出沒有建立索引的表,一旦鎖住數據及爲鎖住整張表。

主鍵索引

CREATE TABLE `test2` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

BEGIN;
-- 主鍵索引
SELECT * from test2 WHERE id = 1 FOR UPDATE;

BEGIN;
-- 手動開啓其他事務
SELECT * from test2 WHERE id = 5 for update;

此時說明,主鍵索引時只會鎖住匹配到的索引項,而不會影響其他事務操作其他索引

唯一索引

CREATE TABLE `test3` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

BEGIN;
-- 唯一索引
SELECT * from test3 where name = '李四' FOR update;

BEGIN;
-- 唯一索引
SELECT * from test3 where id= 5 FOR update;

注意:此時SELECT * from test3 where id= 5 FOR update爲什麼執行卡住了?唯一索引鎖定時,先通過唯一索引然後找到對應主鍵索引,也就是輔助索引--->主鍵索引的一個過程,所以查詢id = 5的數據時也被鎖住了。 通過上面幾個例子發現mysql innodb是通過鎖住索引來實現行鎖的

mysql innodb 爲什麼不會出現幻讀?

詳見下面InnoDB的行鎖,如下圖 臨界鎖的操作

臨界鎖(Next-key Lock)鎖定範圍加記錄

BEGIN;
-- 臨界鎖,鎖住對應的範圍,防止幻讀。
-- 按道理此時應該鎖住,6,7,8(已存在),9,10
SELECT * from test2 WHERE id > 5 and id <11 FOR UPDATE;

BEGIN;
-- 此時測試插入id=7 的值,按道理應該插入不進去,因爲鎖住的範圍是(5,8]和(8,12]
insert into test2 VALUES(7,'試試');

BEGIN;
SELECT * from test2 WHERE id = 12 FOR UPDATE;

剛纔不是id>5 and id <11的麼?此時爲什麼id =12也被鎖了呢?因爲此時鎖住的範圍是(5,8]和(8,12]

Gap Lock(間隙鎖)

BEGIN;
-- 間隙鎖,因爲id =7 這條數據不存在,故鎖退化成了間隙鎖,那麼此時id=7 落在了(5,8)這個區間
SELECT * from test2 where id = 7 for UPDATE;

BEGIN;
-- 因爲鎖退化成了間隙鎖,那麼此時id=7 落在了(5,8)這個區間,故id =6 也一起被鎖住了
INSERT into test2 VALUES(6,'卡卡');

Record Lock(記錄鎖)

注意: 測試在test2表中,也就是主鍵索引。

BEGIN;
-- 在事務1中在id =5 的主鍵項鎖定
SELECT * from test2 where id = 5 for update;

在RR級別中,通過MVCC機制,雖然讓數據變得可重複讀(這就是上面爲什麼事務2也能讀取數據),但我們讀到的數據可能是歷史數據,是不及時的數據,不是數據庫當前的數據!這在一些對於數據的時效特別敏感的業務中,就很可能出問題。

對於這種讀取歷史數據的方式,我們叫它快照讀 (snapshot read),而讀取數據庫當前版本數據的方式,叫當前讀 (current read)。很顯然,在MVCC中:

快照讀:就是select select * from table ....; 當前讀:特殊的讀操作,插入/更新/刪除操作,屬於當前讀,處理的都是當前的數據,需要加鎖。 select * from table where ? lock in share mode; select * from table where ? for update; insert; update ; delete; 事務的隔離級別實際上都是定義了當前讀的級別,MySQL爲了減少鎖處理(包括等待其它鎖)的時間,提升併發能力,引入了快照讀的概念,使得select不用加鎖。而update、insert這些“當前讀”,就需要另外的模塊來解決了

寫("當前讀") 事務的隔離級別中雖然只定義了讀數據的要求,實際上這也可以說是寫數據的要求。上文的“讀”,實際是講的快照讀;而這裏說的“寫”就是當前讀了。 爲了解決當前讀中的幻讀問題,MySQL事務使用了Next-Key鎖。

mysql的索引,爲什麼不用二叉樹,會有什麼樣的問題,紅黑樹呢,爲什麼也不用?

二叉樹深度越高,I/O開銷越大。查詢速率較慢。 紅黑樹(特殊平衡二叉樹)即其子節點的高度差不能大於1,會發生自旋 B樹由於是多路平衡查找樹,可以將每個磁塊的數據放入4KB的數據,充分利用了磁盤的空間,再加上B樹的高度降低,這樣就能快速訪問到數據 B+樹,左閉合B+樹,B+樹非葉子節點不保存數據相關信息,只保存關鍵字和子節點的引用,B+樹葉子節點是順序排序的。

索引引擎,myisam是什麼樣的,innodb呢,這裏面有用到聚集索引嗎?

myisam是索引和數據分開,innodb的數據是索引和數據一起的,myisam沒有聚集索引,這裏也可以看出誰查詢更快,誰寫入。 innodb由聚集索引。 innodb的行鎖通過臨界鎖(gap+recored)來實現機鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章