一、鎖的基本信息:
共享鎖(s):又稱讀鎖。允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
排他鎖(X):又稱寫鎖。允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的數據集共享讀鎖和排他寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。
大家通常以為排他鎖鎖住一行數據后,其他事務就不能讀取和修改該行數據,其實不是這樣的。排他鎖指的是一個事務在一行數據加上排他鎖后,其他事務不能再在其上加其他的鎖。mysql InnoDB引擎默認的修改數據語句:update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型,如果加排他鎖可以使用select …for update語句,加共享鎖可以使用select … lock in share mode語句。所以加過排他鎖的數據行在其他事務種是不能修改數據的,也不能通過for update和lock in share mode鎖的方式查詢數據,但可以直接通過select …from…查詢數據,因為普通查詢沒有任何鎖機制。
意向共享鎖(IS):事務打算給數據行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):事務打算給數據行加排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務;反之,如果兩者兩者不兼容,該事務就要等待鎖釋放。
意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖
下面語句都屬於當前讀,讀取記錄的最新版本。並且,讀取之后,還需要保證其他並發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖)
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?; ##總結:
1.排它鎖X遇到所有的其它鎖,都沖突;2.意向鎖(IX\IS)之間都兼容;3.共享鎖與(or意向)共享鎖(S\IS)之間兼容,與意向排它鎖IX沖突。
二、行鎖與索引:
(1)InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不同,后者是通過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!
在實際應用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導致大量的鎖沖突,從而影響並發性能。
(2)由於MySQL的行鎖是針對索引加的鎖(主鍵是自動加了索引的,但加了索引的字段不是主鍵,所以加了索引但不是主鍵的字段上的數據是可以重復的),不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖沖突的。應用設計的時候要注意這一點。
(3)當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會使用行鎖來對數據加鎖。
(4)即便在條件中使用了索引字段,但是否使用索引來檢索數據是由MySQL通過判斷不同執行計划的代價來決 定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突 時,別忘了檢查SQL的執行計划,以確認是否真正使用了索引。
比如,在tab_with_index表里的name字段有索引,但是name字段是varchar類型的,檢索值的數據類型與索引字段不同,雖然MySQL能夠進行數據類型轉換,但卻不會使用索引,從而導致InnoDB使用表鎖。通過用explain檢查兩條SQL的執行計划,我們可以清楚地看到了這一點:
mysql> explain select * from jjj where a=30; #沒有索引,rows是全部行;
mysql> explain select * from jjj where id=30; #有索引,rows是僅一行!
三、間隙鎖(Next-Key鎖):
1)當我們用范圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的 索引項加鎖;對於鍵值在條件范圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖 (Next-Key鎖)。
舉例來說,假如emp表中只有101條記錄,其empid的值分別是 1,2,…,100,101,下面的SQL:Select * from emp where empid > 100 for update;是一個范圍條件的檢索,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的“間隙”加鎖。
還要特別說明的是,InnoDB除了通過范圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖!比如上面的查詢中,如修改為Select * from emp where empid > 105 for update;(105是不存在的),那么當你插入empid為大於105如200的記錄是不允許的。
2)InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級別的要求,對於上面的例子,要是不使 用間隙鎖,如果其他事務插入了empid大於100的任何記錄,那么本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是為了滿足其恢復和復制的需 要。
3)在實際應用開發中,尤其是並發插入比較多的應用,我們要盡量優化業務邏輯,盡量使用相等條件來訪問更新數據,避免使用范圍條件。
四、事務的隔離性:
事務的隔離性(真正的隔離性):一個事務所做的修改對另一個事務來說是不可見的,就好像是串行執行;官方測試,RR比RC的性能要好。
Innodb沒有庫級別與頁級別的鎖;
1、臟讀的概念測試:
Read uncommitted \ Read committed \Repeatable read \ serializable (RR是真正符合隔離要求的)
Truncate table b;
Set tx_isolation=’ READ-UNCOMMITTED’;
use test; insert into b(2,2); #第一個窗口
select * from b;
#打開另一個b窗口,會發現能查到沒有提交的數據(沒有提交的是臟數據),破壞了隔離性的要求;
2、不可重復讀測試(RC級別時,b一個窗口提交了一個update更新,執行了begin的a窗口本來是停留在b窗口提交前的狀態,第二次查詢時卻發現數據變了,破壞了事務隔離性的要求):
mysql> set tx_isolation='REPEATABLE-READ'; ##a\b窗口都要執行,這里測試RR級別;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges; ##不執行發現會不生效。
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like '%iso%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)
mysql> update jjj set a=42 where id=15; a窗口執行,RR級別不影響修改數據;
mysql> insert into jjj value(21,21); a窗口執行,RR級別不影響修改數據;
mysql> commit; a窗口執行提交;
mysql> select * from jjj where id=15; #b窗口,會發現數據沒有變化(現實了可重復讀)
mysql> select * from jjj; #b窗口,會發現沒有id為21的數據(避免了幻讀)。
此時,會發現給jjj的表加了意向排它鎖IX和record lock:
##最高級別的serializable測試(serializable最嚴格,如果a窗口use bstest;begin; 那么b窗口當insert\update數據時,直接鎖住):
mysql> show variables like '%iso%';
+-----------------------+--------------+
| Variable_name | Value |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
| tx_isolation | SERIALIZABLE |
+-----------------------+--------------+
2 rows in set (0.00 sec)
mysql> insert into jjj value(22,22);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update jjj set a=35 where id=15;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
3、幻讀:即在一個事務中讀到了之前查詢時不存在的數據(如有insert並提交的數據,在RC、RR中不能避免幻讀);
五、行鎖:
1)行鎖的三個模式:1.Record Lock :鎖定的是30這個記錄本身;
2.Gap Lock :鎖定一個范圍,如在查詢范圍的10-30(不包括30本身),即禁止插入10-30之間的記錄,如20,這樣就解決了幻讀問題;
3.Next-key Lock : Gap Lock + Record Lock,即鎖定一個范圍,且鎖定記錄本身,如下例:
mysql> begin;
mysql> update bstest.jjj set a=2222 where id>22;
這里的多條Record已屬於next-key lock
無論是RR還是Rc的隔離級別的update或insert,只要用到了索引,只會根據索引進行加鎖,沒有用到索引則會對所有的記錄加鎖,因為是全表查找(RR的隔離級別才會,RC則不影響其它的修改)
會話1(RR的隔離級別,a列沒有加鎖):
mysql> show variables like '%iso%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)
mysql> update bstest.jjj set a=383 where a=38888;
會話2(RR的隔離級別,a列沒有加鎖):
mysql> show variables like '%iso%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update bstest.jjj set a=15 where a=40; ## 鎖住了,事實上整張表都加上了X lock,無法進行insert 等操作;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
會話1(隔離級別RC,其它同上,a沒有索引):
mysql> show variables like '%iso%';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
| tx_isolation | READ-COMMITTED |
+-----------------------+----------------+
2 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update bstest.jjj set a=222 where a=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
會話2(RC或RR都行,其它同上):
mysql> begin;
mysql> update bstest.jjj set a=15 where a=40;
Query OK, 1 row affected (0.00 sec) #其它行a列正常修改,沒有被鎖住,insert等操作也沒有影響;
Rows matched: 1 Changed: 1 Warnings: 0
mysql> show variables like '%iso%';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
| tx_isolation | READ-COMMITTED |
+-----------------------+----------------+
Xbackup存在問題:細節上(且備份后的數據量與原文件大小一樣)。還是官方的mysqldump比較可靠,且備份的數據量小(邏輯備份)。
六、關於自增列有關的鎖:
事物回滾后,自增值不會跟着回滾,導致自增值不連續,但是這個值連續也沒什么意義、
##自增有關的參數:
auto_increment_increment = 1
auto_increment_offset = 1
1)如果插入前能確定行數的,就是simple inserts(在SQL運行完之前, 確定了自增值之后,就可以釋放自增鎖了)
insert into table_1 values(NULL, 1), (NULL, 2);
2)如果插入前不能確定行數的,就是bulk inserts (在SQL執行完之后,AI鎖才釋放)
insert into table_1 select * from table_2;
innodb_autoinc_lock_mode={0|1|2}
(innodb_autoinc_lock_mode 是read-only 的, 需要修改后重啟MySQL實例)
0 傳統方式
在SQL語句執行完之后,AI鎖才釋放
例如:當insert ... select ... 數據量很大時(比如執行10分鍾),那在這個SQL執行完畢前,其他事物是不能插入的(AI鎖未釋放)
這樣可以保證在這個SQL語句內插入的數據,自增值是連續的,因為在這個10分鍾內,AI自增鎖是被這個SQL持有的,且沒有釋放
1 默認參數( 大部分情況設置為1 )
◾ bulk inserts, 同傳統方式一樣,對於bulk inserts 的方式,和0 - 傳統方式一樣,在SQL執行完之后,AI鎖才釋放
◾ simple inserts, 並發方式,在SQL運行完之前, 確定了自增值之后,就可以釋放自增鎖了
因為bulk inserts 不知道要插入多少行,所以只能等insert結束后,才知道N 的值,然后一次性(ai + N)
而simple inserts 知道插入的行數(M),所以可以先(ai + M) ,然后將鎖釋放掉,給別的事物用,然后自己慢慢插入數據
2
◾ 所有自增都可以並發方式( 不同於Simple inserts的方式)
◾ 同一SQL語句自增可能不連續