MySQL鎖的靈魂七拷問

 

數據和雲
 

 

 

墨墨導讀:本文羅列出有關MySQL鎖的七個問題,這些,你遇到過嗎?html

 

1、緣起java


 

假設你想給別人說明,MySQL 裏面是有鎖的,你會怎麼作?mysql

大多數人,都會開兩個窗口,分別起兩個事務,而後 update 同一條記錄,在發起第二次 update 請求時,block,這樣就說明這行記錄被鎖住了:sql

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

2、禁錮數據庫


問題來了,貌似只有顯式的開啓一個事務,纔會有鎖,若是直接執行一條 update 語句,會不會加鎖呢?安全

好比直接執行:多線程

  1. update t set c = c + 1 where id = 1;併發

這條語句,前面不加 begin,不顯式開啓事務,那麼 MySQL會不會加鎖呢?ide

直覺告訴你,會。性能

可是爲何要加鎖?

給你五秒鐘,說出答案。

...

學過多線程和併發的同窗,都知道下面這段代碼,若是不加鎖,就會有靈異事件:

  1. i++;

開啓十個線程,執行 1000 次這段代碼,最後 i 有極大可能性,會小於 1000。

這時候,用 Java 的套路,加鎖:

  1. synchornize {

  2. i++;

  3. }

問題解決。

同理,對於數據庫,你能夠理解爲 i,就是數據庫裏的一行記錄,i++ 這段代碼,就是一條 update 語句,而多線程,對應的就是數據庫裏的多個事務。

既然對內存中 i 的操做須要加鎖,保證併發安全,那麼對數據庫的記錄進行修改,也必須加鎖。

這道理很簡單,可是不少人,不曾想過。

3、釋然

爲何你們都喜歡用第一部分裏的例子來演示 MySQL 鎖?

由於開兩個事務,會 block,夠直觀。

那麼問題又來了,爲何會 block,或者說,爲何 MySQL 必定要等到 commit 了,纔去釋放鎖?

執行完一條 update 語句,就把鎖釋放了,不行嗎?

舉個例子就知道 MySQL 爲何要這麼幹了:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

一開始數據是:{id:1,c:1};

接着事務A經過 select .. for update,進行當前讀,查到了 c=1;

接着它繼續去更新,把 c 更新成 3,假設這時候,事務 A 執行完 update 語句後,就把鎖釋放了;

那麼就有了第 4 行,事務 B 過來更新,把 c 更新成 4;

結果到了第 5 行,事務 A 又來執行一次當前讀,讀到的 c,居然是 4,明明我上一步才把 c 改爲了 3...

事務 A 不禁的發出怒吼:我爲何會看到了我不應看,我也不想看的東西?!

事務 B 的修改,竟然讓事務 A 看到了,這明目張膽的違反了事務 ACID 中的 I,Isolation,隔離性(事務提交以前,對其餘事務不可見)。

因此,結論:MySQL 爲了知足事務的隔離性,必須在 commit 才釋放鎖。

4、自私的基因

有人說,若是我是讀未提交( Read Uncommited )的隔離級別,能夠讀到對方未提交的東西,是否是就不須要知足隔離性,是否是就能夠不用等到 commit 才釋放鎖了?

非也。

仍是舉例子:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

事務A是 Read Committed,事務B是 Read Uncommitted;

事務B執行了一條 update 語句,把 c 更新成了3

假設事務 B 以爲本身是讀未提交,就把鎖釋放了

那這時候事務 A 過來執行當前讀,讀到了 c 就是3

事務 A 讀到了別的事務沒有提交的東西,而事務 A,還說本身是讀已提交,真是諷刺

根因在於,事務 B 很是自私,他以爲本身是讀未提交,就把鎖釋放了,結果讓別人也被「讀未提交」

顯然,MySQL 不容許這麼自私的行爲存在。

結論:就算你是讀未提交,你也要等到 commit 了再釋放鎖。

5、海納百川

都知道 MySQL 的行鎖,分爲X鎖和S鎖,爲何 MySQL 要這麼作呢?

這個簡單吧,一樣能夠類比 Java 的讀寫鎖:

It allows multiple threads to read a certain resource, but only one to write it, at a time.

容許多個線程同時讀,但只容許一個線程寫,既支持併發提升性能,又保證了併發安全。

6、鳳凰涅磐
最後來個難點的。

假設事務 A 鎖住了表T裏的一行記錄,這時候,你執行了一個 DDL 語句,想給這張表加個字段,這時候須要鎖表吧?可是因爲表裏有一行記錄被鎖住了,因此這時候鎖表時會 block。

那 MySQL 在鎖表時,怎麼判斷表裏有沒有記錄被鎖住呢?

最簡單暴力的,遍歷整張表,遍歷每行記錄,遇到一個鎖,就說明表裏加鎖了。

這樣作能夠,可是很傻,性能不好,高性能的 MySQL,不容許這樣的作法存在。

MySQL 會怎麼作呢?

行鎖是行級別的,粒度比較小,好,那我要你在拿行鎖以前,必須先拿一個假的表鎖,表示你想去鎖住表裏的某一行或者多行記錄。

這樣,MySQL 在判斷表裏有沒有記錄被鎖定,就不須要遍歷整張表了,它只須要看看,有沒有人拿了這個假的表鎖。

這個假的表鎖,就是咱們常說的,意向鎖。

Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table

不少人知道意向鎖是什麼,可是殊不知道爲何須要一個粒度比較大的鎖,不知道它爲什麼而來,不知道 MySQL 爲什麼要設計個意向鎖出來。

知其然,知其因此然。

7、參考文獻

 

  • InnoDB Locking

    https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

  • ReadWriteLock

    http://tutorials.jenkov.com/java-util-concurrent/readwritelock.html

相關文章
相關標籤/搜索