1.數據庫鎖就是為了保證數據庫數據的一致性在一個共享資源被並發訪問時使得數據訪問順序化的機制。MySQL數據庫的鎖機制比較獨特,支持不同的存儲引擎使用不同的鎖機制。
2.MySQL使用了三種類型的鎖機制,分別為:表級鎖,行級鎖,頁級鎖,它們的特性如下所示。
表級鎖:實現邏輯較為簡單,加鎖速度快,開銷小,不會發生死鎖;但粒度最大,發生鎖沖突的幾率最大,並發度最小,適用於以查詢為主,極少量更新的系統。
行級鎖:加鎖慢,開銷大,會發生死鎖;但粒度最小,鎖沖突率小,並發度最高,使用於並發查詢大,有大量按索引條件並發更新少量不同數據的系統。
頁級鎖:介於表級鎖和行級鎖之間。
3.MyISAM表鎖
MyISAM存儲引擎只支持表級鎖,它的鎖模式分為兩種:表共享讀鎖和表獨占寫鎖。MyISAM的讀操作會阻塞其他用戶的寫操作,但不會阻塞其他用戶的讀操作。MyISAM的寫操作會堵塞其他用戶的讀寫操作。MyISAM的讀寫操作是串行的。
查詢表鎖的爭用情況:
mysql> show status like 'table_locks%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Table_locks_immediate | 51 | | Table_locks_waited | 0 | +-----------------------+-------+ 2 rows in set (0.00 sec) -----Table_locks_immediate 立即釋放表鎖數 -----Table_locks_waited 等待表鎖數,該值如較高,說明表鎖爭用較嚴重
MyISAM表寫操作阻塞示例:
SESSION1 | SESSION2 |
獲取stu表的寫鎖 mysql> lock table stu write; |
|
可以進行查詢,更新,插入操作 mysql> select * from stu; mysql> update stu set gender=0 where sno=4010408; |
mysql> select * from stu; 查詢被阻塞,等待鎖釋放 |
釋放表寫鎖 mysql> unlock tables; |
等待 |
獲得鎖,查詢返回 mysql> select * from stu; |
MyISAM表加鎖
MyISAM表在執行select語句前會自動給涉及的所有表加上表讀鎖,執行update,delete,insert等更新語句前,會自動給所有涉及的表加寫鎖,一般不需要顯式地給表加鎖。顯示地給表加鎖,必須要同時取得所有涉及表的鎖,並且不支持鎖升級。在執行lock tables后,只能訪問加鎖的被加鎖的表,不能訪問未加鎖的表。示例如下:
SESSION1 | SESSION2 |
給表stu加上讀鎖 mysql> lock table stu read; |
|
可以查詢stu表 mysql> select * from stu limit 2; |
|
不能查詢stu以外的表 mysql> select * from student limit 2; |
可以查詢或更新未鎖定的表 mysql> select * from student limit 2; |
不能對stu表進行insert,update等操作 mysql> update stu set gender=1 where sno=4010408; |
更新操作會被阻塞 mysql> update stu set gender=1 where sno=4010408;
|
釋放鎖 mysql> unlock tables; |
等待 |
獲得鎖,更新完成 mysql> update stu set gender=1 where sno=4010408; |
使用lock tables鎖定表時,如果查詢用到該表的別名,則須要對別名也進行鎖定,否則會報錯,如下所示:
mysql> lock table stu read; Query OK, 0 rows affected (0.00 sec) mysql> select a.sno from stu a; ERROR 1100 (HY000): Table 'a' was not locked with LOCK TABLES mysql> lock table stu as a read; Query OK, 0 rows affected (0.00 sec) mysql> select a.sno from stu a; +---------+ | sno | +---------+ | 4010404 | | 4010410 | | 4010405 | | 4010408 | | 4010409 | | 4010406 | | 4010407 | +---------+ 7 rows in set (0.00 sec)
MyISAM並發插入,總體而言,MyISAM的讀寫操作是串行的,在某些條件下,也是支持查詢和插入操作的並發進行,MyISAMy引擎有一個系統變量concurrent_insert,可以用來控制並發插入行為,該值有6個內置值選擇,如下所示:
Value | Description |
NEVER (or |
禁止並發插入行為 |
AUTO (or |
在沒有空洞時運行並發插入 |
ALWAYS |
不管有沒有空洞允許表尾並發插入 |
並發插入示例:
SESSION1 | SESSION2 |
鎖定表t1 mysql> lock table t1 read local; |
|
不能進行更新,刪除的等操作 mysql> update t1 set id=5 where id=1; |
可以插入記錄,但更新會阻塞 mysql> insert into t1 values (10); mysql> update t1 set id=70 where id=60; |
不能查看到其他session插入的數據 mysql> select * from t1; |
|
釋放鎖 mysql> unlock tables; |
等待 |
可以查看其他SESSION更新的數據 mysql> select * from t1; |
更新完成 mysql> update t1 set id=70 where id=60; |
MyISAM鎖調度
MyISAM的讀鎖和寫鎖是互斥的,讀操作和寫操作是串行的,一般情況下,寫進程優先獲得鎖,即使是讀進程請求先到鎖等待隊列,寫進程后到。這樣很容易造成大量的更新操作導致查詢操作被永久阻塞,不過可以通過設置low_priority_updates變量值來調節MyISAM引擎的調度機制。
在mysql服務啟動時,指定low_priority_updates參數讓myisam引擎默認給予讀請求更高的優先。
在session中,通過set low_priority_updates來降低當前的會話寫請求的優先級。
4.InnoDB鎖相關
InnoDB引擎對比MyISAM引擎,最大的不同之處在於,InnoDB支持事務和行級鎖。
查看行級鎖爭用情況
mysql> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+ 5 rows in set (0.00 sec) -------如果Innodb_row_lock_waits和Innodb_row_lock_time_avg值較高,則說明行級鎖爭用比較嚴重
InnoDB行鎖分為兩種類型:共享鎖和排他鎖。
共享鎖:允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
給記錄集加共享鎖,前提是當前沒有線程對該結果集中的任何行使用排他鎖,否則申請會阻塞,方式如下:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE mysql> select * from stu lock in share mode;
使用共享鎖線程可對其鎖定記錄進行讀取,其他線程同樣也可對鎖定記錄進行讀取操作,並且這兩個線程讀取的數據都屬於同一個版本。
對於寫入操作,使用共享鎖的線程需要分情況討論,當只有當前線程對指定記錄使用共享鎖時,線程是可對該記錄進行寫入操作(包括更新與刪除),這是由於在寫入操作前,線程向該記錄申請了排他鎖,然后才進行寫入操作;當其他線程也對該記錄使用共享鎖時,則不可進行寫入操作,系統會有報錯提示。不對鎖定記錄使用共享鎖的線程,當然是不可進行寫入操作了,寫入操作會阻塞。
使用共享鎖線程可再次對鎖定記錄申請共享鎖,系統並不報錯,但是操作本身並沒有太大意義。其他線程同樣也可以對鎖定記錄申請共享鎖。
使用共享鎖進程可對其鎖定記錄申請排他鎖;而其他進程是不可以對鎖定記錄申請排他鎖,申請會阻塞。
示例如下:
SESSION1 | SESSION2 |
對stu表所有記錄加共享鎖 mysql> set autocommit=0; mysql> select * from stu lock in share mode; |
|
可以對加鎖記錄進行讀取操作,且也可以對該記錄加共享鎖 mysql> select * from stu lock in share mode; |
|
對鎖定記錄進行更新操作,等待鎖 mysql> update stu set gender=0 where sno=4010406; |
|
也對鎖定記錄進行更新操作,發生死鎖 mysql> update stu set gender=0 where sno=4010406; |
|
獲得鎖,更新完成 mysql> update stu set gender=0 where sno=4010406; |
排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。
給記錄集添加排他鎖,前提是當前沒有線程對該結果集中的任何行使用排他鎖或共享鎖,否則申請會阻塞。方式如下:
SELECT * FROM table_name WHERE ... FOR UPDATE mysql> select * from stu for update;
使用排他鎖線程可以對其鎖定記錄進行讀取,讀取的內容為當前事物的最新版本;而對於不使用排他鎖的線程,同樣是可以進行讀取操作,這種特性是一致性非鎖定讀。
使用排他鎖線程可對其鎖定記錄進行寫入操作;對於不使用排他鎖的線程,對鎖定記錄的寫操作是不允許的,請求會阻塞。
使用排他鎖進程可對其鎖定記錄申請共享鎖,但是申請共享鎖之后,線程並不會釋放原先的排他鎖,因此該記錄對外表現出排他鎖的性質;其他線程是不可對已鎖定記錄申請共享鎖,請求會阻塞。
使用排他鎖進程可對其鎖定記錄申請排他鎖(實際上並沒有任何意義);而其他進程是不可對鎖定記錄申請排他鎖,申請會阻塞。
示例如下:
SESSION1 | SESSION2 |
對表stu所有記錄加排他鎖 mysql> select * from stu for update; |
|
以查詢該記錄,但是不能對該記錄加排他鎖,會等待獲得鎖 mysql> select * from stu; mysql> select * from stu for update; |
|
可以對鎖定記錄進行更新,完成后鎖釋放 mysql> update stu set gender=1 where sno=4010406; mysql> commit; |
|
獲取鎖,返回查詢記錄並加上排他鎖 mysql> select * from stu for update; |
InnoDB行鎖實現方式:InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不同,后者是通過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!
表stu的索引如下所示:
mysql> show index from stu\G *************************** 1. row *************************** Table: stu Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: sno Collation: A Cardinality: 7 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: stu Non_unique: 1 Key_name: idx_name Seq_in_index: 1 Column_name: sname Collation: A Cardinality: 7 Sub_part: NULL Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment:
不使用索引時,會使用表所,示例如下:
SESSION1 | SESSION2 |
給gender=0的記錄加上排他鎖 mysql> select * from stu where gender=0 for update; |
|
給gender=1的記錄加排他鎖,被阻塞 mysql> select * from stu where gender=1 for update; |
使用索引檢索時,使用行鎖,示例如下:
SESSION1 | SESSION2 |
給sname="祝小賢"的記錄加上排他鎖 mysql> select * from stu where sname='祝小賢' for update; |
|
給sname="程小錦"的記錄加上排他鎖 mysql> select * from stu where sname='程小錦' for update; |
由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖沖突的。如下所示:
SESSION1 | SESSION2 |
給sname="祝小賢"和age=20的記錄加上排他鎖 mysql> select * from stu where sno=4010404 and age=20 for update; |
|
給sname="祝小賢"和age=21的記錄加上排他鎖,發生阻塞 mysql> select * from stu where sno=4010404 and age=21 for update; |
即便在條件中使用了索引字段,但是否使用索引來檢索數據是由MySQL通過判斷不同執行計划的代價來決定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時,別忘了檢查SQL的執行計划,以確認是否真正使用了索引。
間隙鎖
給sno>4010409的記錄加排他鎖 mysql> select * from stu where sno>4010409 for update; |
|
向stu中插入一條 mysql> insert into stu values (4010411,'zhangsan','A1114',30,1); |
|
釋放next-key鎖 mysql> rollback; |
|
更新成功 mysql> insert into stu values (4010411,'zhangsan','A1114',30,1); |
使用insert into tbl_name select * from tbl_name或create table tbl_name as select * from tbl_name語句時,innodb會自動對源表加上共享鎖。