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 優化總結
- 檢索數據時,盡量選擇
索引字段作為過濾條件
,防止行鎖升級為表鎖; - 合理設計索引,盡量
縮小鎖的范圍
- 盡量
避免使用范圍檢索
,減小間隙鎖的鎖定范圍 - 盡量
控制事務的大小
,減少鎖定資源的時間和范圍
作者:吳家二少
博客地址:博客園
本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接