INNODB鎖(2)

在上一篇文章寫了鎖的基本概述以及行鎖的三種形式,這一篇的主要內容以下: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;
Query OK, 0 rows affected (0.00 sec)

mysql> update tb1 set a = 13 where a = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0ide

#開啓一個事務B,更新同一條數據性能

3

#這時候RR和RC隔離級別,查詢到的數據都是以下(都解決了髒讀問題):測試

mysql> select * from tb1 where a = 5;
+---+
| a |
+---+
| 5 |
+---+
1 row in set (0.00 sec)大數據

 
4  

#提交事務

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

5

#在RR的隔離級別下數據讀到的數據以下:讀取事務開始時的版本

mysql> select * from tb1 where a = 5;
+---+
| a |
+---+
| 5 |
+---+
1 row in set (0.00 sec)

 
6

#在RC的隔離級別下讀到的數據以下:老是讀取最新的一份快照數據。

mysql> select * from tb1 where a = 5;
Empty set (0.00 sec)

#這裏咱們提到過,同一個事務中兩次讀到的數據並不同,其實違反了事務的隔離性,出現了幻讀!

 

 自增加和鎖

自增加在數據庫中是很是常見的一種屬性,也是不少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。在繼續討論新的自增加實現方式以前,須要對自增加的插入進行分類。以下說明:

  • insert-like:指全部的插入語句,如INSERT、REPLACE、INSERT…SELECT,REPLACE…SELECT、LOAD DATA等。
  • simple inserts:指能在插入前就肯定插入行數的語句,這些語句包括INSERT、REPLACE等。須要注意的是:simple inserts不包含INSERT…ON DUPLICATE KEY UPDATE這類SQL語句。
  • bulk inserts:指在插入前不能肯定獲得插入行數的語句,如INSERT…SELECT,REPLACE…SELECT,LOAD DATA。
  • mixed-mode inserts:指插入中有一部分的值是自增加的,有一部分是肯定的。入INSERT INTO t1(c1,c2) VALUES(1,’a’),(2,’a’),(3,’a’);也能夠是指INSERT…ON DUPLICATE KEY UPDATE這類SQL語句。

接下來分析參數innodb_autoinc_lock_mode以及各個設置下對自增加的影響,其總共有三個有效值可供設定,即0、一、2,具體說明以下:

  • 0:這是MySQL 5.1.22版本以前自增加的實現方式,即經過表鎖的AUTO-INC Locking方式,由於有了新的自增加實現方式,0這個選項不該該是新版用戶的首選了。
  • 1:這是該參數的默認值,對於」simple inserts」,該值會用互斥量(mutex)去對內存中的計數器進行累加的操做。對於」bulk inserts」,仍是使用傳統表鎖的AUTO-INC Locking方式。在這種配置下,若是不考慮回滾操做,對於自增值列的增加仍是連續的。而且在這種方式下,statement-based方式的replication仍是能很好地工做。須要注意的是,若是已經使用AUTO-INC Locing方式去產生自增加的值,而這時須要再進行」simple inserts」的操做時,仍是須要等待AUTO-INC Locking的釋放。
  • 2:在這個模式下,對於全部」INSERT-LIKE」自增加值的產生都是經過互斥量,而不是AUTO-INC Locking的方式。顯然,這是性能最高的方式。然而,這會帶來必定的問題,由於併發插入的存在,在每次插入時,自增加的值可能不是連續的。此外,最重要的是,基於Statement-Base Replication會出現問題。所以,使用這個模式,任什麼時候候都應該使用row-base replication。這樣才能保證最大的併發性能及replication主從數據的一致。
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

相關文章
相關標籤/搜索