對於MySQL中的樂觀鎖和悲觀鎖,可能不少的開發者還不是很熟悉,並不知道其中具體是如何實現的。本文就針對這個問題作一個實際案例演示,讓你完全明白這兩種鎖的區別。mysql
以前針對MySQL中的鎖單獨分享過一篇文章,對於MySQL鎖還不夠了解的能夠仔細閱讀如下該文。sql
MySQL的中鎖按照範圍主要分爲表鎖、行鎖和頁面鎖。其中myisam存儲引擎只支持表鎖,InnoDB不只僅支持行鎖,在必定程度上也支持表鎖。按照行爲能夠分爲共享鎖(讀鎖)、排他鎖(寫鎖)和意向鎖。按照思想分爲樂觀鎖和悲觀鎖。併發
今天的文章演示一下實際中的樂觀鎖和悲觀鎖是如何操做的。ide
下面的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;
插入模擬數據spa
BEGIN; INSERT INTO `user` VALUES (0000000001, '張三', 0, '18228937997@163.com', '18228937997', 1); INSERT INTO `user` VALUES (0000000002, '李四', 0, '1005349393@163.com', '15683202302', 1); INSERT INTO `user` VALUES (0000000003, '李四1', 0, '1005349393@163.com', '15683202302', 1); INSERT INTO `user` VALUES (0000000004, '李四2', 0, '1005349393@163.com', '15683202302', 1); INSERT INTO `user` VALUES (0000000005, '李四3', 0, '1005349393@163.com', '15683202302', 1); INSERT INTO `user` VALUES (0000000006, '李四4', 0, '1005349393@163.com', '15683202302', 1); INSERT INTO `user` VALUES (0000000007, '李四55', 0, '1005349393@163.com', '15683202302', 1); COMMIT;
表中數據。code
mysql root@127.0.0.1:demo> select * from user; +----+--------+-----+---------------------+-------------+---------+ | id | name | sex | email | mobile | version | +----+--------+-----+---------------------+-------------+---------+ | 1 | 張三 | 0 | 18228937997@163.com | 18228937997 | 2 | | 2 | 李四 | 0 | 1005349393@163.com | 15683202302 | 1 | | 3 | 李四1 | 0 | 1005349393@163.com | 15683202302 | 1 | | 4 | 李四2 | 0 | 1005349393@163.com | 15683202302 | 1 | | 5 | 李四3 | 0 | 1005349393@163.com | 15683202302 | 1 | | 6 | 李四4 | 0 | 1005349393@163.com | 15683202302 | 1 | | 7 | 李四55 | 0 | 1005349393@163.com | 15683202302 | 1 | +----+--------+-----+---------------------+-------------+---------+ 7 rows in set Time: 0.011s
悲觀鎖,比較消極的一種鎖處理方式。直接在操做數據時,搶佔鎖。其餘的事務在進行時就會等待,直到佔有鎖的事務釋放鎖爲止。blog
這種處理方式能保證數據的最大一致性,可是容易致使鎖超時、併發程度低等問題。 首先咱們開啓事務一,而且對id=1的數據進行update操做,此時咱們不提交事務。事務
mysql root@127.0.0.1:demo> begin; Query OK, 0 rows affected Time: 0.002s mysql root@127.0.0.1:demo> update `user` set name = '張三111111'where id = 1; Query OK, 1 row affected Time: 0.004s
接着咱們開啓事務二,對id=1的數據進行update操做,查看此時會發生什麼狀況?ci
mysql root@127.0.0.1:demo> begin; Query OK, 0 rows affected Time: 0.002s mysql root@127.0.0.1:demo> update `user` set sex = 1 where id = 1;
咱們執行完update語句以後,就處於等待狀態,SQL語句也不會立刻被執行,這是由於事務一沒有commit,也就沒有釋放id=1的數據對應的寫鎖。效果以下圖:
經過上面的例子,咱們就能比較直觀的感覺到悲觀鎖的實現過程是如何的。
樂觀鎖認爲數據通常狀況下不會形成衝突,只有當數據去執行修改狀況時,纔會針對數據衝突作處理。這裏是如何發現衝突了呢?常規的方式,都是在數據行上加一個版本號或者時間戳等字段。(本文使用version做爲版本好方式,使用時間戳方式同理)
樂觀鎖的實現原理:
一個事務在讀取數據時,將對應的版本號字段讀取出來,假設此時的版本號是1。
另一個事務也是執行一樣的讀取操做。當事務一提交時,對版本號執行+1,此時該數據行的版本號就是2。
第二個事務執行修改操做時,針對業務數據作條件,並默認增長一個版本號做爲where條件。此時修改語句中的版本號字段是不知足where條件,該事務執行失敗。經過這種方式來達到鎖的功能。
客戶端一
mysql root@127.0.0.1:demo> select * from user where id = 1; +----+------------+-----+---------------------+-------------+---------+ | id | name | sex | email | mobile | version | +----+------------+-----+---------------------+-------------+---------+ | 1 | 張三111111 | 0 | 18228937997@163.com | 18228937997 | 1 | +----+------------+-----+---------------------+-------------+---------+ 1 row in set Time: 0.012s mysql root@127.0.0.1:demo> update `user` set name = '事務一', version = version + 1 where id = 1 and version = 1; Query OK, 1 row affected Time: 0.008s mysql root@127.0.0.1:demo> select * from user where id = 1; +----+--------+-----+---------------------+-------------+---------+ | id | name | sex | email | mobile | version | +----+--------+-----+---------------------+-------------+---------+ | 1 | 事務一 | 1 | 18228937997@163.com | 18228937997 | 2 | +----+--------+-----+---------------------+-------------+---------+ 1 row in set Time: 0.009s
執行update語句的順序應該在客戶端二執行了select以後,在執行。
客戶端二
mysql root@127.0.0.1:demo> select * from user where id = 1; +----+------------+-----+---------------------+-------------+---------+ | id | name | sex | email | mobile | version | +----+------------+-----+---------------------+-------------+---------+ | 1 | 張三111111 | 1 | 18228937997@163.com | 18228937997 | 1 | +----+------------+-----+---------------------+-------------+---------+ 1 row in set Time: 0.015s mysql root@127.0.0.1:demo> update `user` set name = '事務二', version = version + 1 where id = 1 and version = 1; Query OK, 0 rows affected Time: 0.003s mysql root@127.0.0.1:demo> select * from user where id = 1; +----+--------+-----+---------------------+-------------+---------+ | id | name | sex | email | mobile | version | +----+--------+-----+---------------------+-------------+---------+ | 1 | 事務一 | 1 | 18228937997@163.com | 18228937997 | 2 | +----+--------+-----+---------------------+-------------+---------+ 1 row in set Time: 0.012s
此時根據update返回的結構,能夠看出受影響的行數爲0,同時select查詢以後,返現數據也是事務一的數據。
悲觀鎖:比較適合寫入操做比較頻繁的場景,若是出現大量的讀取操做,每次讀取的時候都會進行加鎖,這樣會增長大量的鎖的開銷,下降了系統的吞吐量。
樂觀鎖:比較適合讀取操做比較頻繁的場景,若是出現大量的寫入操做,數據發生衝突的可能性就會增大,爲了保證數據的一致性,應用層須要不斷的從新獲取數據,這樣會增長大量的查詢操做,下降了系統的吞吐量。
兩種所各有優缺點,讀取頻繁使用樂觀鎖,寫入頻繁使用悲觀鎖。
像樂觀鎖適用於寫比較少的狀況下,即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是常常產生衝突,上層應用會不斷的進行retry,這樣反卻是下降了性能,因此這種狀況下用悲觀鎖就比較合適,之因此用悲觀鎖就是由於兩個用戶更新同一條數據的機率高,也就是衝突比較嚴重的狀況下,因此才用悲觀鎖。
悲觀鎖比較適合強一致性的場景,但效率比較低,特別是讀的併發低。樂觀鎖則適用於讀多寫少,併發衝突少的場景。