在上一篇文章寫了鎖的基本概述以及行鎖的三種形式,這一篇的主要內容以下:mysql
一致性非鎖定讀是InnoDB經過多版本併發控制(MVCC,multi version concurrency control)的方式來讀取當前執行時間數據庫中的最近一次快照,若是讀取的行正在執行DELETE、UPDATE操做,這時讀取操做不會等待行上鎖的釋放,相反,InnoDB存儲引擎會去讀取行的一個快照數據,以下圖:sql
上圖直觀地展現了InnoDB存儲引擎一致性的非鎖定讀,之因此稱其爲非鎖定讀,由於不須要等待訪問的行上X鎖的釋放。快照數據是指該行以前版本的數據,該實現是經過Undo段來實現,而Undo用來在事務中回滾數據,所以快照數據自己是沒有額外的開銷。此外,讀取快照數據是不須要上鎖的,由於沒有必要對歷史的數據進行修改。數據庫
能夠看到,非鎖定讀的機制大大提升了數據讀取的併發性,在InnoDB存儲由於默認設置下,這是默認的讀取方式,即讀取不會佔用和等待表上的鎖。可是在不一樣事務隔離級別下,讀取的方式不一樣,並非每一個事務隔離級別下讀取的都是一致性讀。一樣,即便都是使用一致性讀,可是對於快照數據的定義也不相同。架構
經過上圖,咱們能夠看出快照數據其實就是當前數據以前的歷史版本,可能有多個版本。一個行可能又不止一個快照數據。咱們稱這種技術爲行多版本技術。由此帶來的併發控制,稱之爲多版本併發控制(MVCC,multi version concurrency control)併發
在READ COMMITTED和REPEATABLE READ下,InnoDB存儲引擎使用非鎖定的一致性讀。然而,對於快照數據的定義卻不相同。在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀老是讀取被鎖定行的最新一份快照數據。在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀老是讀取事務開始時的行數據版本。下面看一個列子:oracle
時間序列 | 會話A | 會話B |
1 | mysql> begin; #開啓一個事務 Query OK, 0 rows affected (0.00 sec) mysql> select * from tb1 where a = 5; +---+ | a | +---+ | 5 | +---+ 1 row in set (0.00 sec) |
|
2 | mysql> begin; #開啓一個事務B,更新同一條數據性能 |
|
3 | #這時候RR和RC隔離級別,查詢到的數據都是以下(都解決了髒讀問題):測試 mysql> select * from tb1 where a = 5; |
|
4 | #提交事務 mysql> commit; |
|
5 | #在RR的隔離級別下數據讀到的數據以下:讀取事務開始時的版本 mysql> select * from tb1 where a = 5; |
|
6 | #在RC的隔離級別下讀到的數據以下:老是讀取最新的一份快照數據。 mysql> select * from tb1 where a = 5; |
自增加在數據庫中是很是常見的一種屬性,也是不少DBA或開發人員首選的主鍵方式。在InnoDB存儲引擎的內存結構中,對每一個含有自增加值的表都有一個自增加計數器。當對含有自增加的計數器的表進行插入操做時,這個計數器會被初始化,執行以下的語句來獲得計數器的值:
select max(auto_inc_col) from t for update;
插入操做會依據這個自增加的計數器值加1賦予自增加列。這個實現方式稱爲AUTO-INC Locking。這種鎖實際上是採用一種特殊的表鎖機制,爲了提升插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增加值插入的SQL語句後當即釋放。【注意自增鎖釋放的時機】
雖然AUTO-INC Locking從必定程度上提升了併發插入的效率,但仍是存在一些性能上的問題。首先,對於有自增加值的列的併發插入性能較差,事務必須等待前一個插入的完成,雖然不用等待事務的完成。其次,對於INSERT….SELECT的大數據的插入會影響插入的性能,由於另外一個事務中的插入會被阻塞。
從MySQL 5.1.22版本開始,InnoDB存儲引擎中提供了一種輕量級互斥量的自增加實現機制,這種機制大大提升了自增加值插入的性能。而且從該版本開始,InnoDB存儲引擎提供了一個參數innodb_autoinc_lock_mode來控制自增加的模式,該參數的默認值爲1。在繼續討論新的自增加實現方式以前,須要對自增加的插入進行分類。以下說明:
接下來分析參數innodb_autoinc_lock_mode以及各個設置下對自增加的影響,其總共有三個有效值可供設定,即0、一、2,具體說明以下:
mysql> show variables like "innodb_autoinc_lock_mode"; #這個數值默認是1,而且是個只讀的變量,不能改變,能夠從源碼改變 +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_autoinc_lock_mode | 1 | +--------------------------+-------+ 1 row in set (0.00 sec) mysql> set global innodb_autoinc_lock_mode = 2; ERROR 1238 (HY000): Variable 'innodb_autoinc_lock_mode' is a read only variable
這裏須要特別注意,InnoDB跟MyISAM不一樣,MyISAM存儲引擎是表鎖設計,自增加不用考慮併發插入的問題。所以在master上用InnoDB存儲引擎,在slave上用MyISAM存儲引擎的replication架構下,用戶能夠考慮這種狀況。
另外,InnoDB存儲引擎,自增持列必須是索引,同時必須是索引的第一個列,若是不是第一個列,會拋出異常,而MyiSAM不會有這個問題。
在給一個字段設置自增以後,從起始值開始,每次加1,那麼這個起始值和步長是如下兩個參數控制的:
mysql> show variables like "auto_increment%"; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | auto_increment_increment | 1 | #設置自增值的起始值 | auto_increment_offset | 1 | #設置自增值的步長 +--------------------------+-------+ 2 rows in set (0.00 sec)
mysql> set auto_increment_increment = 2; #設置起始值爲2 Query OK, 0 rows affected (0.00 sec) mysql> set auto_increment_offset = 2; #設置步長爲2 Query OK, 0 rows affected (0.00 sec) mysql> create table test1(id int auto_increment primary key, name varchar(20)); #建立表,插入測試數據 Query OK, 0 rows affected (0.05 sec) mysql> insert into test1(name) values("zhao"); Query OK, 1 row affected (0.00 sec) mysql> insert into test1(name) values("qian"); Query OK, 1 row affected (0.01 sec) mysql> insert into test1(name) values("sun"); Query OK, 1 row affected (0.00 sec) mysql> select * from test1; +----+------+ | id | name | +----+------+ | 2 | zhao | | 4 | qian | | 6 | sun | +----+------+ 3 rows in set (0.00 sec)
簡單說一下外鍵,外鍵主要用於引用完整性的約束檢查。在InnoDB存儲引擎中,對於一個外鍵列,若是沒有顯示地對這個列加索引,InnoDB存儲引擎會自動對其加一個索引,由於這樣能夠避免表鎖。這比Oracle數據庫作得好,Oracle數據庫不會自動添加索引,用戶必須本身手動添加,這也致使了Oracle數據庫中可能產生死鎖。
對於外鍵值的插入或更新,首先須要檢查父表中的記錄,既SELECT父表。可是對於父表的SELECT操做,不是使用一致性非鎖定讀的方式,由於這會發生數據不一致的問題,所以這時使用的是SELECT…LOCK IN SHARE MODE方式,即主動對父表加一個S鎖。若是這時父表上已經這樣加X鎖,子表上的操做會被阻塞,以下:
實例以下:
# 建立parent表; create table parent( tag_id int primary key auto_increment not null, tag_name varchar(20) ); # 建立child表; create table child( article_id int primary key auto_increment not null, article_tag int(11), CONSTRAINT tag_at FOREIGN KEY (article_tag) REFERENCES parent(tag_id) ); # 插入數據; insert into parent(tag_name) values('mysql'); insert into parent(tag_name) values('oracle'); insert into parent(tag_name) values('mariadb');
開始測試
# Session A mysql> begin mysql> delete from parent where tag_id = 3; # Session B mysql> begin mysql> insert into child(article_id,article_tag) values(1,3); #阻塞
第二列是外鍵,執行該語句時被阻塞。
在上述的例子中,兩個會話中的事務都沒有進行COMMIT或ROLLBACK操做,而會話B的操做會被阻塞。這是由於tag_id爲3的父表在會話中已經加了一個X鎖,而此時在會話B中用戶又須要對父表中tag_id爲3的行加一個S鎖,這時INSERT的操做會被阻塞。設想若是訪問父表時,使用的是一致性的非鎖定讀,這時Session B會讀到父表有tag_id=3的記錄,能夠進行插入操做。可是若是會話A對事務提交了,則父表中就不存在tag_id爲3的記錄。數據在父、子表就會存在不一致的狀況。若這時用戶查詢INNODB_LOCKS表,會看到以下結果:
mysql> select * from information_schema.innodb_locks\G *************************** 1. row *************************** lock_id: 3359:35:3:4 lock_trx_id: 3359 lock_mode: S lock_type: RECORD lock_table: `test`.`parent` lock_index: PRIMARY lock_space: 35 lock_page: 3 lock_rec: 4 lock_data: 3 *************************** 2. row *************************** lock_id: 3358:35:3:4 lock_trx_id: 3358 lock_mode: X lock_type: RECORD lock_table: `test`.`parent` lock_index: PRIMARY lock_space: 35 lock_page: 3 lock_rec: 4 lock_data: 3 2 rows in set, 1 warning (0.00 sec)
從鎖結構能夠看出,對於parent表加了兩個鎖,一個S鎖和一個X鎖。
博文基本摘自inside君的《MySQL技術內幕--INNODB存儲引擎》,實際地址來自:http://www.ywnds.com/?p=9129