MySQL學習筆記十六:鎖機制


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;
Query OK, 0 rows affected (0.03 sec)

 

可以進行查詢,更新,插入操作

mysql> select * from stu;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 1 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (0.03 sec)

mysql> update stu set gender=0 where sno=4010408;
Query OK, 1 row affected (0.11 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> select * from stu;

查詢被阻塞,等待鎖釋放

釋放表寫鎖

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

等待
 

獲得鎖,查詢返回

mysql> select * from stu;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (4 min 27.54 sec)

MyISAM表

MyISAM表在執行select語句前會自動給涉及的所有表加上表讀鎖,執行update,delete,insert等更新語句前,會自動給所有涉及的表加寫鎖,一般不需要顯式地給表加鎖。顯示地給表加鎖,必須要同時取得所有涉及表的鎖,並且不支持鎖升級。在執行lock tables后,只能訪問加鎖的被加鎖的表,不能訪問未加鎖的表。示例如下:

SESSION1 SESSION2

給表stu加上讀鎖

mysql> lock table stu read;
Query OK, 0 rows affected (0.49 sec)

 

可以查詢stu表

mysql> select * from stu limit 2;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
+---------+--------+-------+------+--------+
2 rows in set (0.15 sec)

 

 不能查詢stu以外的表

mysql> select * from student limit 2;
ERROR 1100 (HY000): Table 'student' was not locked with LOCK TABLES

可以查詢或更新未鎖定的表 

mysql> select * from student limit 2;
+---------+-----------+--------+------+---------+
| sno | sname | sclass | sage | sgender |
+---------+-----------+--------+------+---------+
| 4010404 | zhumuxian | A1114 | 20 | 男 |
+---------+-----------+--------+------+---------+
1 row in set (0.29 sec)

不能對stu表進行insert,update等操作

mysql> update stu set gender=1 where sno=4010408;
ERROR 1099 (HY000): Table 'stu' was locked with a READ lock and can't be updated

 更新操作會被阻塞

mysql> update stu set gender=1 where sno=4010408;

 

 釋放鎖

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

 等待
 

 獲得鎖,更新完成

mysql> update stu set gender=1 where sno=4010408;
Query OK, 1 row affected (5 min 23.36 sec)
Rows matched: 1 Changed: 1 Warnings: 0

使用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
0)

禁止並發插入行為

AUTO (or
1)

在沒有空洞時運行並發插入

ALWAYS
(or 2)

不管有沒有空洞允許表尾並發插入

並發插入示例:

SESSION1 SESSION2

鎖定表t1

mysql> lock table t1 read local;
Query OK, 0 rows affected (0.00 sec)

 

 不能進行更新,刪除的等操作

mysql> update t1 set id=5 where id=1;
ERROR 1099 (HY000): Table 't1' was locked with a READ lock and can't be updated

 可以插入記錄,但更新會阻塞

mysql> insert into t1 values (10);
Query OK, 1 row affected (0.12 sec)

mysql> update t1 set id=70 where id=60;

 不能查看到其他session插入的數據

mysql> select * from t1;
+----+
| id |
+----+
| 1 |
| 4 |
| 30 |
| 60 |
+----+
4 rows in set (0.00 sec)

 

釋放鎖

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

 等待

 可以查看其他SESSION更新的數據

mysql> select * from t1;
+----+
| id |
+----+
| 1 |
| 4 |
| 10 |
| 30 |
| 70 |
+----+
5 rows in set (0.00 sec)

更新完成

mysql> update t1 set id=70 where id=60;
Query OK, 1 row affected (7 min 44.10 sec)
Rows matched: 1 Changed: 1 Warnings: 0

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;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from stu lock in share mode;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (0.00 sec)

 
 

 可以對加鎖記錄進行讀取操作,且也可以對該記錄加共享鎖

mysql> select * from stu lock in share mode;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (0.00 sec)

 對鎖定記錄進行更新操作,等待鎖

mysql> update stu set gender=0 where sno=4010406;

 
 

 也對鎖定記錄進行更新操作,發生死鎖

mysql> update stu set gender=0 where sno=4010406;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 獲得鎖,更新完成

mysql> update stu set gender=0 where sno=4010406;
Query OK, 1 row affected (10.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0

 

排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。

給記錄集添加排他鎖,前提是當前沒有線程對該結果集中的任何行使用排他鎖或共享鎖,否則申請會阻塞。方式如下:

SELECT * FROM table_name WHERE ... FOR UPDATE
mysql> select * from stu for update;

使用排他鎖線程可以對其鎖定記錄進行讀取,讀取的內容為當前事物的最新版本;而對於不使用排他鎖的線程,同樣是可以進行讀取操作,這種特性是一致性非鎖定讀。

使用排他鎖線程可對其鎖定記錄進行寫入操作;對於不使用排他鎖的線程,對鎖定記錄的寫操作是不允許的,請求會阻塞。

使用排他鎖進程可對其鎖定記錄申請共享鎖,但是申請共享鎖之后,線程並不會釋放原先的排他鎖,因此該記錄對外表現出排他鎖的性質;其他線程是不可對已鎖定記錄申請共享鎖,請求會阻塞。

使用排他鎖進程可對其鎖定記錄申請排他鎖(實際上並沒有任何意義);而其他進程是不可對鎖定記錄申請排他鎖,申請會阻塞。

示例如下:

SESSION1 SESSION2

對表stu所有記錄加排他鎖

mysql> select * from stu for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 0 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (0.00 sec)

 
 

 以查詢該記錄,但是不能對該記錄加排他鎖,會等待獲得鎖

mysql> select * from stu;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (0.00 sec)

mysql> select * from stu for update;

 可以對鎖定記錄進行更新,完成后鎖釋放

mysql> update stu set gender=1 where sno=4010406;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

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

 
 

 獲取鎖,返回查詢記錄並加上排他鎖

mysql> select * from stu for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010406 | 鍾小喜 | A1014 | 24 | 1 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
7 rows in set (7.65 sec)

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;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010405 | 肖小傑 | A1013 | 22 | 0 |
| 4010407 | 鍾小惠 | A1015 | 26 | 0 |
| 4010408 | 肖小傑 | A1114 | 21 | 0 |
| 4010409 | 鍾小兆 | A1114 | 22 | 0 |
+---------+--------+-------+------+--------+

 
 

 給gender=1的記錄加排他鎖,被阻塞

mysql> select * from stu where gender=1 for update;

使用索引檢索時,使用行鎖,示例如下:

SESSION1 SESSION2

給sname="祝小賢"的記錄加上排他鎖

mysql> select * from stu where sname='祝小賢' for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
+---------+--------+-------+------+--------+

 
 

 給sname="程小錦"的記錄加上排他鎖

mysql> select * from stu where sname='程小錦' for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
1 row in set (0.04 sec)

由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖沖突的。如下所示:

SESSION1 SESSION2

給sname="祝小賢"和age=20的記錄加上排他鎖

mysql> select * from stu where sno=4010404 and age=20 for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010404 | 祝小賢 | A1012 | 20 | 1 |
+---------+--------+-------+------+--------+

 
 

給sname="祝小賢"和age=21的記錄加上排他鎖,發生阻塞

mysql> select * from stu where sno=4010404 and age=21 for update;

即便在條件中使用了索引字段,但是否使用索引來檢索數據是由MySQL通過判斷不同執行計划的代價來決定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時,別忘了檢查SQL的執行計划,以確認是否真正使用了索引。

間隙鎖

當我們用范圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,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使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級別的要求,對於上面的例子,要是不使用間隙鎖,如果其他事務插入了empid大於100的任何記錄,那么本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是為了滿足其恢復和復制的需要。
如下所示:
   

給sno>4010409的記錄加排他鎖

mysql> select * from stu where sno>4010409 for update;
+---------+--------+-------+------+--------+
| sno | sname | class | age | gender |
+---------+--------+-------+------+--------+
| 4010410 | 程小錦 | A1114 | 22 | 1 |
+---------+--------+-------+------+--------+
1 row in set (0.03 sec)

 
 

向stu中插入一條 

mysql> insert into stu values (4010411,'zhangsan','A1114',30,1);

釋放next-key鎖

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

 
 

更新成功

mysql> insert into stu values (4010411,'zhangsan','A1114',30,1);
Query OK, 1 row affected (27.06 sec)

使用insert into tbl_name select * from tbl_name或create table tbl_name as select * from tbl_name語句時,innodb會自動對源表加上共享鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM