MySQL學習之路(一)鎖機制


1 鎖的分類

1.1 操作類型

  • 讀鎖(共享鎖):針對同一份數據,多個操作可以同時進行而不會互相影響
  • 寫鎖(排它鎖):當寫操作沒有完成前,它會阻塞其他讀鎖或者寫鎖

1.2 操作粒度

  • 表鎖:鎖住整張表
  • 行鎖:鎖住某行表記錄
  • 間隙鎖:鎖住某個區間行記錄

2 表鎖(偏讀鎖)

偏向MyISAM引擎,開銷小,加鎖快;無死鎖;鎖粒度大,發生鎖沖突的概率最高,並發度最低

2.1 創建新表

CREATE TABLE csde_myisam (
`id` VARCHAR(64),
`user_name` VARCHAR(512) not null,
`password` VARCHAR(256),
`display_name` VARCHAR(128),
primary key (`id`))ENGINE myisam;

2.2 插入數據插入數據

INSERT INTO csde_myisam
(id, user_name, password, display_name) 
VALUES
('1', 'kai', '123', 'wukai'), 
('2', 'jay', '123', 'jayy'), 
('3', 'beasyer', '123', 'beasyer liu');

2.3 查看表鎖情況

SHOW OPEN TABLES;

In_use

  • 0:沒有加表鎖
  • 1:加了表鎖
mysql> SHOW OPEN TABLES;
+--------------------+---------------------------+--------+-------------+
| Database           | Table                     | In_use | Name_locked |
+--------------------+---------------------------+--------+-------------+
| mysql              | index_stats               |      0 |           0 |
....
| mysql              | gtid_executed             |      0 |           0 |
| information_schema | SHOW_STATISTICS           |      0 |           0 |
| mysql              | component                 |      0 |           0 |
| mysql              | columns                   |      0 |           0 |
| kaiwu3             | csde_myisam               |      1 |           0 |
| mysql              | func                      |      0 |           0 |
| information_schema | COLUMNS                   |      0 |           0 |
| mysql              | events                    |      0 |           0 |
| mysql              | catalogs                  |      0 |           0 |
| kaiwu3             | csde                      |      0 |           0 |
| mysql              | collations                |      0 |           0 |
| mysql              | table_partitions          |      0 |           0 |
| information_schema | TABLES                    |      0 |           0 |
| mysql              | time_zone_transition_type |      0 |           0 |
| mysql              | tablespaces               |      0 |           0 |
+--------------------+---------------------------+--------+-------------+
54 rows in set (0.00 sec)

2.4 手動增加表鎖

LOCK TABLE table_name READ/WRITE, table_name2 READ/WRITE;

2.5 釋放表鎖

UNLOCK TABLES;

2.6 案例分析

# 給csde_myisam這張表加讀鎖==>myisam只支持表鎖,不支持行鎖
LOCK TABLE csde_myisam READ, csde WRITE;

2.6.1 表讀鎖案例分析

Session-1 Session-2
mysql> lock table csde_myisam read;
mysql> select * from csde_myisam;
+----+-------------+------------+-----------------+
| id   | user_name | password  | display_name  |
+----+-------------+------------+-----------------+
| 1    | kai             | 123          | wukai             |
| 2    | jay             | 123          | jayy               |
| 3    | beasyer      | 123          | beasyer liu      |
+----+-------------+------------+-----------------+
  mysql> select * from csde;
ERROR 1100 (HY000): Table 'csde' was not locked with LOCK TABLES
當前csde_myisam表被鎖,釋放后當前session才可以操作其他表
 
 
 
 
mysql> update csde_myisam set password='111' where id='1';
ERROR 1099 (HY000): Table 'csde_myisam' was locked with a READ lock and can't be updated
 
mysql> unlock tables;
 
當前session對鎖定表可讀,不可寫,不可讀其他表

mysql> select * from csde_myisam;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai             | 123         | wukai            |
| 2    | jay             | 123         | jayy              |
| 3    | beasyer      | 123         | beasyer liu    |
+----+-------------+-----------+----------------+
mysql> select * from csde;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai             | 123        | wuka              |
| 2    | jay             | 123        | jayy               |
| 3    | beasyer      | 123        | beasyer liu      |
+----+-------------+-----------+----------------+
 
mysql> update csde_myisam set password='111' where id='1';
blocking....
 
Query OK, 1 row affected (1 min 55.56 sec)==>update success.
當前session對鎖定表可讀,寫阻塞,可讀其他表

分析:寫操作(update, insert, delete)都會自動添加寫鎖(排它鎖)。

2.6.2 表寫鎖案例分析

Session-1 Session-2
mysql> lock table csde_myisam write;
mysql> select * from csde_myisam;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | wukai             |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
mysql> select * from csde;
ERROR 1100 (HY000): Table 'csde' was not locked with LOCK TABLES
當前csde_myisam表被鎖,釋放后當前session才可以操作其他表
 
mysql> update csde_myisam set password='111' where id='1';
Query OK, 0 rows affected (0.00 sec)==>update success
 
mysql> unlock tables;
 
當前session對鎖定表可讀,可寫,不可讀其他表

mysql> select * from csde_myisam;
blocking...
==>取消
mysql> select * from csde;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai             | 123         | wukai            |
| 2    | jay             | 123         | jayy              |
| 3    | beasyer      | 123         | beasyer liu    |
+----+-------------+-----------+----------------+
 
 
mysql> update csde_myisam set password='111' where id='1';
blocking....
 
Query OK, 1 row affected (1 min 55.56 sec)==>update success.
當前session對鎖定表不可讀,不可寫,對其他表可讀寫

2.7 總結

MyISAM在執行select語句前,會自動給涉及的表加讀鎖,執行寫操作之前,會自動給涉及的表加寫鎖。

  • 對MyISAM的讀操作(加讀鎖):不會阻塞其他session對同一表的讀操作,但是會阻塞其他session的寫操作,直到讀鎖釋放。
  • 對MyISAM的寫操作(加寫鎖):會阻塞其他session對同一表的讀寫操作,直到寫鎖釋放。

3 行鎖

  • 偏向InnodB引擎,開銷大,加鎖慢;會出現死鎖;鎖粒度最小,發生鎖沖突的概率最低,並發度也最高。
  • InnoDB與MyISAM的最大不同有兩點:InnoDB支持事務,行鎖MyISAM不支持事務,只支持表鎖
  • InnodDB引擎,添加行鎖需要針對索引字段過濾,對非索引字段進行過濾,行鎖會失效升級為表鎖。注意:針對索引字段進行過濾時,如果索引失效,同樣會將行鎖升級為表鎖(開發巨坑,勿踩)。

3.1 創建新表

CREATE TABLE csde_innodb (
`id` VARCHAR(64),
`user_name` VARCHAR(512) NOT NULL,
`password` VARCHAR(256),
`display_name` VARCHAR(128),
primary key (`id`))ENGINE INNODB;

3.2 插入數據

INSERT INTO csde_innodb
(id, user_name, password, display_name) 
VALUES
('1', 'kai', '123', 'wukai'), 
('2', 'jay', '123', 'jayy'), 
('3', 'beasyer', '123', 'beasyer liu');

3.3 創建索引

  • 行級鎖需要加在索引字段上,否則會升級為表鎖
ALTER TABLE csde_innodb ADD INDEX idx_user_name(user_name);

3.4 關閉自動提交

  • 正常情況下,輸入";"會自動提交,關閉自動提交后,必須手動輸入“commit;”才會真正提交
  • Innodb引擎下,寫操作(update, insert, delete)的過濾條件為索引字段時針對該索引字段添加行鎖。這里關閉自動提交,主要用於方便debug
SET autocommit=0; # 每個session需單獨設置

3.5 添加行鎖的方法

  • 對數據進行寫操作時,數據庫會自動為該行記錄添加行鎖(過濾條件為索引字段)
  • 主動針對某一行添加行鎖(過濾條件為索引字段)
SELECT * FROM csde_innodb WHERE id=4 FOR UPDATE;

3.6 使用默認隔離級別--可重復讀

MySQL事務的隔離級別分別為:

  • 讀未提交(Read Uncommitted)
  • 讀已提交(Read Committed)
  • 可重復讀(Repeatable Read)
  • 串行化(Serializable)

關於事務隔離,后續會單獨再寫一篇博客,這里就不詳細介紹了。

mysql> SELECT @@global.transaction_isolation;
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ                |
+--------------------------------+

mysql> SELECT @@session.transaction_isolation;
+---------------------------------+
| @@session.transaction_isolation |
+---------------------------------+
| REPEATABLE-READ                 |
+---------------------------------+

3.7 行鎖案例分析

Session-1 Session-2
select * from csde_inndob;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | kai                 |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
 
update csde_innodb set display_name='wukai' where id='1';
select * from csde_innodb;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | wukai             |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
 
 
commit;
 
 
 
 
 
 
 
 
 update csde_innodb set display_name='wukai' where id='1'; (添加行鎖)
 
commit;
select * from csde_innodb;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | wukai             |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
commit;
select * from csde_innodb;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 124         | wukai             |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
select * from innodb
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | kai                 |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
 
 
 select * from csde_innodb;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | kai                 |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
*沒有提交,還在上個事務中,可重復讀
 
commit;
select * from csde_innodb;
+----+-------------+-----------+----------------+
| id   | user_name | password | display_name |
+----+-------------+-----------+----------------+
| 1    | kai            | 123         | wukai             |
| 2    | jay            | 123         | jayy               |
| 3    | beasyer     | 123         | beasyer liu     |
+----+-------------+-----------+----------------+
 
update csde_innodb set password='124' where id='1';
blacking...
 
Query OK...
 
 
 
commit;

3.8 行鎖失效--升級表鎖

前面我們說過,要想行鎖生效,過濾條件(where)一定要加在索引字段上,否則將升級為表鎖。但是,在實際開發過程中,由於雖然過濾條件為索引字段,但真正的查詢不一定是走索引的--索引失效,此時行鎖同樣會升級為表鎖。

查詢csde_innodb這張表,存在兩個單鍵索引(Primary(id), idx_user_name(user_name)

mysql> show index from csde_innodb;
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table       | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| csde_innodb |          0 | PRIMARY       |            1 | id          | A         |           3 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| csde_innodb |          1 | idx_user_name |            1 | user_name   | A         |           3 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+

3.8.1 非索引字段

Session-1 Session-2
update csde_innodb set password='125' where password='124';
Query OK..(過濾條件為password,非索引字段)
行鎖轉換為 表鎖...
 
commit;(表鎖移除)

update csde_innodb set display_name='jay yu' where user_name='jay';
Blocking...
雖然這里user_name為索引字段,且修改的和session-1不是同一行記錄,但是此時表被鎖。
 
Query OK...(此時user_name='jay‘這行數據被加行鎖)
commit;(行鎖移除)

3.8.2 索引失效

老開發應該都非常熟悉,並非針對索引行過濾就一定會生效,某些場景下,即使針對索引過濾,依舊會存在索引不生效的案例。這里簡單介紹幾種索引失效場景:

  • 字段類型轉換
  • 前導模糊查詢
  • 數據庫執行計算
  • 不遵循最左前綴匹配規則

關於索引失效或者優化問題,后續會單獨再出一篇文章,這里就不詳細展開介紹了。

Session-1 Session-2
update csde_innodb set password='321' where id=1;
Query OK..(過濾條件為id,索引字段,但類型由varchar轉換為int,索引失效)
行鎖升級為 表鎖...
 
commit;(表鎖移除)

UPDATE csde_innodb SET display_name='jay yu' WHERE user_name='jay';
Blocking...
雖然這里user_name為索引字段,且修改的和session-1非同一行記錄,但是此時表被鎖。
 
Query OK...(此時user_name='jay‘這行數據被加行鎖)
commit;(行鎖移除)

3.9 查看行鎖信息

mysql> SHOW STATUS LIKE 'innodb_row_lock%';
+-------------------------------+--------+
| Variable_name                 | Value  |
+-------------------------------+--------+
| Innodb_row_lock_current_waits | 1      |
| Innodb_row_lock_time          | 482516 |
| Innodb_row_lock_time_avg      | 10489  |
| Innodb_row_lock_time_max      | 51036  |
| Innodb_row_lock_waits         | 46     |
+-------------------------------+--------+
  • Innodb_row_lock_current_waits: 當前正在鎖定等待的數量
  • Innodb_row_lock_time: 從系統啟動到現在鎖定的總時間
  • Innodb_row_lock_time_avg:每次等待花費的平均時間
  • Innodb_row_lock_time_max: 鎖定等待花費的最長時間
  • Innodb_row_lock_waits:從系統啟動到現在鎖定等待的總次數

當數據庫的總等待時間比較長,平均等待時間比較高,等待鎖定的總次數比較時,應該排查系統,進行相應的優化。

4 間隙鎖

間隙鎖(Next-Key): 針對某個索引字段,鎖定一定范圍的行記錄。

  • 過濾索引字段為范圍時,Mysql自動給該范圍的行記錄添加間隙鎖;
  • 可重復讀(Mysql默認)隔離級別下,普通索引字段添加行鎖時自動升級為間隙鎖,間隙為上下兩條表記錄(左閉右開區間),主鍵索引添加行鎖時則為普通行鎖

4.1 修改表結構

mysql> ALTER TABLE csde_innodb CHANGE COLUMN id id INTEGER(74) NOT NULL;
mysql> ALTER TABLE csde_innodb CHANGE COLUMN password password INTEGER(64);
mysql> ALTER TABLE csde_innodb ADD COLUMN number INTEGER(64);

4.2 查看表結構

mysql> DESC csde_innodb;
+--------------+--------------+------+-----+---------+-------+
| Field        | Type         | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id           | int          | NO   | PRI | NULL    |       |
| user_name    | varchar(256) | NO   | MUL | NULL    |       |
| password     | int          | YES  |     | NULL    |       |
| display_name | varchar(128) | YES  |     | NULL    |       |
| number       | int          | YES  | MUL | NULL    |       |
+--------------+--------------+------+-----+---------+-------+

4.3 添加索引

mysql> ALTER TABLE csde_innodb ADD INDEX idx_numer(number);

4.4 查看索引

mysql> SHOW INDEX FROM csde_innodb;
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table       | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| csde_innodb |          0 | PRIMARY       |            1 | id          | A         |           3 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| csde_innodb |          1 | idx_user_name |            1 | user_name   | A         |           3 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| csde_innodb |          1 | idx_numer     |            1 | number      | A         |           1 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+-------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+

4.5 案例分析

4.5.1 普通索引

默認隔離級別,根據普通索引字段過濾,針對某一行記錄進行寫操作或者主動添加行鎖,將自動升級為間隙鎖,間隙為排序后上下表記錄左閉右開區間

Session-1 Session-2
mysql> select * from csde_innodb;
+----+-------------+-----------+----------------+----------+
 | id  | user_name | password | display_name | number |
+----+-------------+-----------+----------------+----------+
 | 1   | kai            | 666         | wukai3           | 3           |
 | 2   | jay            | 666         | jay yu            | 7           |
 | 3   | beasyer     | 666         | beasyer liu     | 9           |
 | 4   | neal          | 123         | neal chen       | 4           |
+----+-------------+-----------+----------------+----------+
mysql> update csde_innodb set password=125 where number=4;
自動添加間隙鎖[3,7)
 
 
 
 
 
 
 
 
 
 
commit;(間隙鎖鎖移除)
mysql> select * from csde_innodb;
+----+-------------+-----------+----------------+----------+
 | id  | user_name | password | display_name | number |
+----+-------------+-----------+----------------+----------+
 | 1   | kai            | 666         | wukai3           | 3           |
 | 2   | jay            | 666         | jay yu            | 7           |
 | 3   | beasyer     | 666         | beasyer liu     | 9           |
 | 4   | neal          | 123         | neal chen       | 4           |
+----+-------------+-----------+----------------+----------+
 
 
mysql> insert into csde_innodb(id, user_name, password,display_name, number) values(5, 'bevis', 100, 'bevis duan', 3);
Blocking... 
mysql> insert into csde_innodb(id, user_name, password,display_name, number) values(5, 'bevis', 100, 'bevis duan', 6);
Blocking...
mysql> insert into csde_innodb(id, user_name, password,display_name, number) values(5, 'bevis', 100, 'bevis duan', 7);
Query OK...
mysql> insert into csde_innodb(id, user_name, password,display_name, number) values(6, 'bevis', 100, 'bevis duan', 2);
Query OK...

4.5.2 主鍵索引

默認隔離級別下,根據主鍵索引字段過濾,對某一行記錄進行寫操作或者主動添加行鎖,僅添加普通的行鎖

Session-1 Session-2
mysql> select * from csde_innodb;
+----+-------------+-----------+----------------+----------+
| id   | user_name | password | display_name | number |
+----+-------------+-----------+----------------+----------+
| 1    | kai            | 666         | wukai3           | 3           |
| 2    | jay            | 666         | jay yu            | 7           |
| 3    | beasyer     | 666         | beasyer liu      | 9          |
| 4    | neal          | 123         | neal chen        | 4          |
| 7    | bevis         | 100         | bevis duan      | 3          |
+----+-------------+-----------+-----------------+---------+
mysql> update csde_innodb set password=123 where id=4;
自動添加行鎖
 
 
 
  commit;(行鎖移除)
 
mysql> select * from csde_innodb;
+----+-------------+-----------+----------------+----------+
| id   | user_name | password | display_name | number |
+----+-------------+-----------+----------------+----------+
| 1    | kai            | 666         | wukai3           | 3           |
| 2    | jay            | 666         | jay yu            | 7           |
| 3    | beasyer     | 666         | beasyer liu      | 9          |
| 4    | neal          | 123         | neal chen        | 4          |
| 7    | bevis         | 100         | bevis duan      | 3          |
+----+-------------+-----------+-----------------+---------+
 
mysql> insert into csde_innodb(id, user_name, password,display_name, number) values(5, 'bevis', 100, 'bevis duan', 3);
Query OK...(不存在間隙鎖)
mysql> update csde_innodb set number=6 where id=4;
Blocking...(存在行鎖)
 
Query OK...

5 優化總結

  • 檢索數據時,盡量選擇索引字段作為過濾條件,防止行鎖升級為表鎖;
  • 合理設計索引,盡量縮小鎖的范圍
  • 盡量避免使用范圍檢索,減小間隙鎖的鎖定范圍
  • 盡量控制事務的大小,減少鎖定資源的時間和范圍

作者:吳家二少
博客地址:博客園
本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接


免責聲明!

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



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