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就會發現莫名其妙多了一條以前沒有的數據,這就是幻讀,不能經過行鎖來避免。 剛纔提到鎖機制,那麼咱們最多見的就是悲觀鎖和樂觀鎖。mysql

悲觀鎖和樂觀鎖

悲觀鎖

爲了保證事務的隔離性,就須要一致性鎖定讀,就是每次操做數據時都去鎖住數據,無論哪一種操做(增、刪、改、查)都加鎖,以致於其餘事務操做這些數據sql

樂觀鎖

通常都是給表新增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是經過鎖住索引來實現行鎖的code

mysql innodb 爲何不會出現幻讀?

詳見下面InnoDB的行鎖,以下圖 臨界鎖的操做 blog

臨界鎖(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)來實現機鎖。

相關文章
相關標籤/搜索