紙上得來終覺淺,絕知此事要躬行。 鎖的分類 從對數據操作的粒度分 : 表鎖:操作時,會鎖定整個表。 行鎖:操作時,會鎖定當前操作行。 從對數據操作的類型分: 讀鎖(共用鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響。 寫鎖(排它鎖):當前操作沒有完成之前,它會阻斷其他寫鎖和讀鎖。 MyS ...
紙上得來終覺淺,絕知此事要躬行。
鎖的分類
從對數據操作的粒度分 :
- 表鎖:操作時,會鎖定整個表。
- 行鎖:操作時,會鎖定當前操作行。
從對數據操作的類型分:
- 讀鎖(共用鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響。
- 寫鎖(排它鎖):當前操作沒有完成之前,它會阻斷其他寫鎖和讀鎖。
MySQL鎖
MySQL的鎖機制比較簡單,其最顯著的特點是不同的存儲引擎支持不同的鎖機制。下表中羅列出了各存儲引擎對鎖的支持情況:
存儲引擎 | 表級鎖 | 行級鎖 | 頁面鎖 |
---|---|---|---|
MyISAM | 支持 | 不支持 | 不支持 |
InnoDB | 支持 | 支持 | 不支持 |
MEMORY | 支持 | 不支持 | 不支持 |
BDB | 支持 | 不支持 | 支持 |
特性:
鎖類型 | 特點 |
---|---|
表級鎖 | 偏向MyISAM 存儲引擎,開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。 |
行級鎖 | 偏向InnoDB 存儲引擎,開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。 |
頁面鎖 | 開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。 |
MyISAM
表鎖
如何加表鎖
MyISAM
在執行查詢語句SELECT
前,會自動給涉及的所有表加讀鎖,在執行更新操作UPDATE、DELETE、INSERT
前,會自動給涉及的表加寫鎖,這個過程並不需要用戶干預,因此,用戶一般不需要直接用 LOCK TABLE
命令給 MyISAM 表顯式加鎖。
顯示加表鎖語法:
加讀鎖 : lock table table_name read;
加寫鎖 : lock table table_name write;
雖然MyIASM
預設加了表鎖,但是我們仍然可以手動加鎖,具體通過案例來瞭解一下加鎖之後,我們操作資料庫會有什麼影響。
讀寫鎖案例
在進行測試之前我們需要準備測試環境和相關資料庫以及表,並且把MySQL
的存儲引擎指定為MyISAM
,如下先創建一個資料庫並切換到資料庫:
create database demo2 default charset=utf8;
接著創建表,本次我們測試的是MyISAM
存儲引擎,所以要創建表時要指定存儲引擎(說明存儲引擎是作用於表,而不是作用於資料庫,也就是說一個資料庫有多個表,而多個表可以使用不同的存儲引擎)
CREATE TABLE `tb_book` (
`id` INT(11) auto_increment,
`name` VARCHAR(50) DEFAULT NULL,
`publish_time` DATE DEFAULT NULL,
`status` CHAR(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;
# 插入數據
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'高性能MySQL','2088-08-01','1');
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'MySQL技術內幕','2088-08-08','0');
CREATE TABLE `tb_user` (
`id` INT(11) auto_increment,
`name` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;
# 插入數據
INSERT INTO tb_user (id, name) VALUES(NULL,'張三');
INSERT INTO tb_user (id, name) VALUES(NULL,'李四');
讀鎖
數據準備完成接下來我們進行測試,使用兩個客戶端連接資料庫。
- 查詢數據
客戶端一:
- 獲取
tb_book
表的讀鎖
lock table tb_book read;
- 執行查詢語句
select * from tb_book;
mysql> select * from tb_book;
+----+-------------------+--------------+--------+
| id | name | publish_time | status |
+----+-------------------+--------------+--------+
| 1 | 高性能MySQL | 2088-08-01 | 1 |
| 2 | MySQL技術內幕 | 2088-08-08 | 0 |
+----+-------------------+--------------+--------+
2 rows in set (0.00 sec)
可以正常執行,查詢出數據。
客戶端二:
- 執行查詢操作
select * from tb_book;
mysql> select * from tb_book;
+----+-------------------+--------------+--------+
| id | name | publish_time | status |
+----+-------------------+--------------+--------+
| 1 | 高性能MySQL | 2088-08-01 | 1 |
| 2 | MySQL技術內幕 | 2088-08-08 | 0 |
+----+-------------------+--------------+--------+
2 rows in set (0.00 sec)
也可以正常查詢數據
客戶端一:
- 查詢未鎖定的表
select * from tb_user;
mysql> select * from tb_user;
ERROR 1100 (HY000): Table 'tb_user' was not locked with LOCK TABLES
提示tb_user
沒有加鎖,無法獲取數據
客戶端二:
- 查詢未鎖定的表
select * from tb_user;
mysql> select * from tb_user;
+----+--------+
| id | name |
+----+--------+
| 1 | 張三 |
| 2 | 李四 |
+----+--------+
2 rows in set (0.00 sec)
可以正常查詢出未鎖定的表
- 插入數據
客戶端一:
- 執行插入數據
insert into tb_book values(null,'Mysql高級','2088-01-01','1');
mysql> insert into tb_book values(null,'Mysql高級','2088-01-01','1');
ERROR 1099 (HY000): Table 'tb_book' was locked with a READ lock and can't be updated
執行插入, 直接報錯,由於當前tb_book 獲得的是讀鎖,不能執行更新操作。
客戶端二:
- 執行插入數據
insert into tb_book values(null,'Mysql高級','2088-01-01','1');
執行語句阻塞,等待中。
當在客戶端一中釋放鎖指令unlock tables
後 , 客戶端二中的 inesrt
語句立即執行。
寫鎖
- 查詢數據
客戶端一:
- 獲取tb_book表的寫鎖
lock table tb_book write;
- 執行查詢數據
select * from tb_book;
mysql> select * from tb_book;
+----+-------------------+--------------+--------+
| id | name | publish_time | status |
+----+-------------------+--------------+--------+
| 1 | 高性能MySQL | 2088-08-01 | 1 |
| 2 | MySQL技術內幕 | 2088-08-08 | 0 |
| 3 | Mysql高級 | 2088-01-01 | 1 |
+----+-------------------+--------------+--------+
3 rows in set (0.00 sec)
- 執行
update
操作
mysql> update tb_book set name = 'MySQL' where id = 3;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
- 執行
insert
操作
mysql> insert into tb_book values(null,'Python','2088-01-01','1');
Query OK, 1 row affected (0.00 sec)
- 執行
delete
操作
mysql> delete from tb_book where id = 4;
Query OK, 1 row affected (0.00 sec)
當前客戶端處於write lock
,其增刪改查都正常執行
客戶端二:
- 執行查詢操作
select * from tb_book;
此時查詢操作處於阻塞狀態。
客戶端三:
- 執行更新操作
update tb_book set name = 'Java' where id = 3;
也處於阻塞狀態。
當我們把客戶端一的表解鎖,則客戶端二的查詢操作和客戶端三的更新操作立刻執行。
鎖上加鎖
- 不同客戶端加鎖
客戶端一:
- 獲取
tb_book
表的讀鎖
lock table tb_book read;
客戶端二:
- 獲取
tb_book
表的讀鎖
lock table tb_book read;
此時客戶端一,只能讀取加鎖的表,客戶端二同樣只能讀取加鎖的表,都無法更新表。
- 獲取
tb_book
表的寫鎖
lock table tb_book wtite;
由於客戶端一已經上了讀鎖,此時客戶端二上寫鎖會阻塞。
- 同一客戶端多次加鎖
客戶端一:
- 執行
tb_book
表的讀鎖,然後繼續執行寫鎖
lock table tb_book read;
lock table tb_book write;
此時客戶端一可以對加鎖的表執行增刪改查,客戶端二對加鎖的表增刪改查阻塞。
總結
- 對
MyISAM
表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求 - 對
MyISAM
表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作
簡而言之,就是讀鎖會阻塞寫,但是不會阻塞讀。而寫鎖,則既會阻塞讀,又會阻塞寫。
此外,MyISAM 的讀寫鎖調度是寫優先,這也是MyISAM不適合做寫為主的表的存儲引擎的原因。因為寫鎖後,其他線程不能做任何操作,大量的更新會使查詢很難得到鎖,從而造成永遠阻塞。
查詢鎖的爭用情況
show open tables;
In_user
:表當前被查詢使用的次數。如果該數為零,則表是打開的,但是當前沒有被使用。
Name_locked
:表名稱是否被鎖定。名稱鎖定用於取消表或對錶進行重命名等操作。
show status like 'Table_locks%';
mysql> show status like 'Table_locks%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 139 |
| Table_locks_waited | 0 |
+-----------------------+-------+
2 rows in set (0.00 sec)
Table_locks_immediate
:指的是能夠立即獲得表級鎖的次數,每立即獲取鎖,值加1。
Table_locks_waited
:指的是不能立即獲取表級鎖而需要等待的次數,每等待一次,該值加1,此值高說明存在著較為嚴重的表級鎖爭用情況。