天天看點

#yyds幹貨盤點#盤點MySQL的鎖機制是如何實作的

文章介紹

對于MySQL中的樂觀鎖和悲觀鎖,可能很多的開發者還不是很熟悉,并不知道其中具體是如何實作的。本文就針對這個問題做一個實際案例示範,讓你徹底明白這兩種鎖的差別。

鎖分類

MySQL的中鎖按照範圍主要分為表鎖、行鎖和頁面鎖。其中myisam存儲引擎隻支援表鎖,InnoDB不僅僅支援行鎖,在一定程度上也支援表鎖。按照行為可以分為共享鎖(讀鎖)、排他鎖(寫鎖)和意向鎖。按照思想分為樂觀鎖和悲觀鎖。

今天的文章示範一下實際中的樂觀鎖和悲觀鎖是如何操作的。

表結構

下面的SQL語句是表的結構。

CREATE TABLE `demo`.`user`  (
  `id` int(10) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `sex` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `version` int(1) NULL DEFAULT 1 COMMENT '資料版本号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
           

插入模拟資料

BEGIN;
INSERT INTO `user` VALUES (0000000001, '張三', 0, '[email protected]', '18228937997', 1);
INSERT INTO `user` VALUES (0000000002, '李四', 0, '[email protected]', '15683202302', 1);
INSERT INTO `user` VALUES (0000000003, '李四1', 0, '[email protected]', '15683202302', 1);
INSERT INTO `user` VALUES (0000000004, '李四2', 0, '[email protected]', '15683202302', 1);
INSERT INTO `user` VALUES (0000000005, '李四3', 0, '[email protected]', '15683202302', 1);
INSERT INTO `user` VALUES (0000000006, '李四4', 0, '[email protected]', '15683202302', 1);
INSERT INTO `user` VALUES (0000000007, '李四55', 0, '[email protected]', '15683202302', 1);
COMMIT;
           

表中資料。

mysql [email protected]:demo> select * from user;
+----+--------+-----+---------------------+-------------+---------+
| id | name   | sex | email               | mobile      | version |
+----+--------+-----+---------------------+-------------+---------+
| 1  | 張三   | 0   | [email protected] | 18228937997 | 2       |
| 2  | 李四   | 0   | [email protected]  | 15683202302 | 1       |
| 3  | 李四1  | 0   | [email protected]  | 15683202302 | 1       |
| 4  | 李四2  | 0   | [email protected]  | 15683202302 | 1       |
| 5  | 李四3  | 0   | [email protected]  | 15683202302 | 1       |
| 6  | 李四4  | 0   | [email protected]  | 15683202302 | 1       |
| 7  | 李四55 | 0   | [email protected]  | 15683202302 | 1       |
+----+--------+-----+---------------------+-------------+---------+
7 rows in set
Time: 0.011s
           

悲觀鎖

悲觀鎖,比較消極的一種鎖處理方式。直接在操作資料時,搶占鎖。其他的事務在進行時就會等待,直到占有鎖的事務釋放鎖為止。

這種處理方式能保證資料的最大一緻性,但是容易導緻鎖逾時、并發程度低等問題。

首先我們開啟事務一,并且對id=1的資料進行update操作,此時我們不送出事務。

mysql [email protected]:demo> begin;
Query OK, 0 rows affected
Time: 0.002s
mysql [email protected]:demo> update `user` set  name = '張三111111'where id = 1;
Query OK, 1 row affected
Time: 0.004s
           

接着我們開啟事務二,對id=1的資料進行update操作,檢視此時會發生什麼情況?

mysql [email protected]:demo> begin;
Query OK, 0 rows affected
Time: 0.002s
mysql [email protected]:demo> update `user` set  sex = 1 where id = 1;
           

我們執行完update語句之後,就處于等待狀态,SQL語句也不會馬上被執行,這是因為事務一沒有commit,也就沒有釋放id=1的資料對應的寫鎖。效果如下圖:

#yyds幹貨盤點#盤點MySQL的鎖機制是如何實作的
通過上面的例子,我們就能比較直覺的感受到悲觀鎖的實作過程是如何的。

樂觀鎖

樂觀鎖認為資料一般情況下不會造成沖突,隻有當資料去執行修改情況時,才會針對資料沖突做處理。這裡是如何發現沖突了呢?正常的方式,都是在資料行上加一個版本号或者時間戳等字段。(本文使用version作為版本好方式,使用時間戳方式同理)

樂觀鎖的實作原理:

  1. 一個事務在讀取資料時,将對應的版本号字段讀取出來,假設此時的版本号是1。
  2. 另外一個事務也是執行同樣的讀取操作。當事務一送出時,對版本号執行+1,此時該資料行的版本号就是2。
  3. 第二個事務執行修改操作時,針對業務資料做條件,并預設增加一個版本号作為where條件。此時修改語句中的版本号字段是不滿足where條件,該事務執行失敗。通過這種方式來達到鎖的功能。
#yyds幹貨盤點#盤點MySQL的鎖機制是如何實作的

用戶端一

mysql [email protected]:demo> select * from user where id = 1;
+----+------------+-----+---------------------+-------------+---------+
| id | name       | sex | email               | mobile      | version |
+----+------------+-----+---------------------+-------------+---------+
| 1  | 張三111111 | 0   | [email protected] | 18228937997 | 1       |
+----+------------+-----+---------------------+-------------+---------+
1 row in set
Time: 0.012s
mysql [email protected]:demo> update `user` set  name = '事務一', version = version + 1  where id = 1 and version = 1;
Query OK, 1 row affected
Time: 0.008s
mysql [email protected]:demo> select * from user where id = 1;
+----+--------+-----+---------------------+-------------+---------+
| id | name   | sex | email               | mobile      | version |
+----+--------+-----+---------------------+-------------+---------+
| 1  | 事務一 | 1   | [email protected] | 18228937997  |  2      |
+----+--------+-----+---------------------+-------------+---------+
1 row in set
Time: 0.009s
           
執行update語句的順序應該在用戶端二執行了select之後,在執行。

用戶端二

mysql [email protected]:demo> select * from user where id = 1;
+----+------------+-----+---------------------+-------------+---------+
| id | name       | sex | email               | mobile      | version |
+----+------------+-----+---------------------+-------------+---------+
| 1  | 張三111111 | 1   | [email protected] | 18228937997 | 1       |
+----+------------+-----+---------------------+-------------+---------+
1 row in set
Time: 0.015s
mysql [email protected]:demo> update `user` set  name = '事務二', version = version + 1  where id = 1 and version = 1;
Query OK, 0 rows affected
Time: 0.003s
mysql [email protected]:demo> select * from user where id = 1;
+----+--------+-----+---------------------+-------------+---------+
| id | name   | sex | email               | mobile      | version |
+----+--------+-----+---------------------+-------------+---------+
| 1  | 事務一 | 1   | [email protected] | 18228937997 | 2       |
+----+--------+-----+---------------------+-------------+---------+
1 row in set
Time: 0.012s
           

此時根據update傳回的結構,可以看出受影響的行數為0,同時select查詢之後,返現資料也是事務一的資料。

适用場景

悲觀鎖:比較适合寫入操作比較頻繁的場景,如果出現大量的讀取操作,每次讀取的時候都會進行加鎖,這樣會增加大量的鎖的開銷,降低了系統的吞吐量。

樂觀鎖:比較适合讀取操作比較頻繁的場景,如果出現大量的寫入操作,資料發生沖突的可能性就會增大,為了保證資料的一緻性,應用層需要不斷的重新擷取資料,這樣會增加大量的查詢操作,降低了系統的吞吐量。

總結