1. 程式人生 > >mysql的鎖--行鎖,表鎖,樂觀鎖,悲觀鎖

mysql的鎖--行鎖,表鎖,樂觀鎖,悲觀鎖

引言--為什麼mysql提供了鎖

  最近看到了mysql有行鎖和表鎖兩個概念,越想越疑惑。為什麼mysql要提供鎖機制,而且這種機制不是一個擺設,還有很多人在用在現代資料庫裡幾乎有事務機制,acid的機制應該能解決併發排程的問題了,為什麼還要主動加鎖呢?

  後來看到一篇文章防止更新丟失,並不能單靠資料庫事務控制器來解決,需要應用程式對要更新的資料加必要的鎖來解決”。瞬間,世界觀都崩塌了。非常不敢相信,於是自己寫了程式碼檢驗一下。

  資料庫表是這樣的。用count欄位來做100次累加。


為了保證實驗的科學性,先確認了資料庫是InnoDB的查看錶使用的儲存引擎:show table status from db_name where name='table_name';

,這樣才有事務機制;也確認了隔離性級別select @@session.tx_isolation;


定義一個任務,讀count值--> 程式count++-->寫資料庫

publicclass LostUpdate implements Runnable{

privateCountDownLatch countDown;

public LostUpdate(CountDownLatch countDown){

this.countDown = countDown;

    }

    @Override

publicvoid run() {

        Connection conn=null

;

try {

            Class.forName("com.mysql.jdbc.Driver");

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8",

                    "root", "123");

        } catch (Exception e) {

            e.printStackTrace();

return;

        }

try {

conn.setAutoCommit

(false);

//不加鎖的情況

            PreparedStatement ps =conn.prepareStatement("select * from LostUpdate where id =1");

//加鎖的情況

            //PreparedStatement ps =conn.prepareStatement("select * from LostUpdate where id =1 for update");

            ResultSet rs=ps.executeQuery();

int count = 0;

while(rs.next()){

                count= rs.getInt("count");

            }

            count++;

            ps =conn.prepareStatement("update LostUpdate set count=? where id =1");

            ps.setInt(1, count);

            ps.executeUpdate();

conn.commit();

        } catch (Exception e) {

try {

                conn.rollback();

            } catch (SQLException e1) {

                e1.printStackTrace();

            }

            e.printStackTrace();

        }

//表示一次任務完成        countDown.countDown();

    }

}

 主執行緒下建立子執行緒,模擬多執行緒環境

publicclass TestLock {    

publicstaticvoid main(String[] args) throws InterruptedException {

//建立執行緒池,裡面有10個執行緒,共執行100+1操作

finalint THREAD_COUNT=10;

finalint RUN_TIME=100;

        ExecutorService threadPool=Executors.newFixedThreadPool(THREAD_COUNT);

//CountDownLatch保證主執行緒等待所有任務完成

        CountDownLatch count=newCountDownLatch(RUN_TIME);

for(int i=0;i<RUN_TIME;i++)

threadPool.execute(new LostUpdate(count));

        threadPool.shutdown();

        count.await();

//提示所有任務執行完

        System.out.println("finish");

    }

}

執行結果是:

 

  大概解釋一下程式,就是建立了一個執行緒池,裡面10個執行緒,執行100次任務。每個任務就是 讀count值--程式count++--寫資料庫,經典的銀行存款(丟失修改)問題。事實勝於雄辯,結論就是上面的橙色字,解決丟失修改不能靠事務,要加必要的鎖,所以資料庫提供的鎖不是個擺設。

資料庫事務機制

  為了找到問題的根源,為了拯救我崩潰的世界觀,我又去回顧了資料庫事務的知識。借鑑這篇

資料庫的acid屬性

· 原性性(Actomicity):事務是一個原子操作單元,其對資料的修改,要麼全都執行,要麼全都不執行

· 一致性(Consistent):在事務開始和完成時,資料都必須保持一致狀態。這意味著所有相關的資料規則都必須應用於事務的修改,以完整性;事務結束時,所有的內部資料結構(如B樹索引或雙向連結串列)也都必須是正確的。

· 隔離性(Isolation):資料庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的“獨立”環境執行。這意味著事務處理過程中的中間狀態對外部是不可見的,反之亦然。

· 永續性(Durable):事務完成之後,它對於資料的修改是永久性的即使出現系統故障也能夠保持

  說好的一致性呢,童話裡都是騙人的!!  

事務併發排程的問題

1. 髒讀(dirty read):A事務讀取B事務尚未提交的更改資料,並在這個資料基礎上操作。如果B事務回滾,那麼A事務讀到的資料根本不是合法的,稱為髒讀。在oracle中,由於有version控制,不會出現髒讀。

2. 不可重複讀(unrepeatable read):A事務讀取了B事務已經提交的更改(或刪除)資料。比如A事務第一次讀取資料,然後B事務更改該資料並提交,A事務再次讀取資料,兩次讀取的資料不一樣。

3. 幻讀(phantom read):A事務讀取了B事務已經提交的新增資料。注意和不可重複讀的區別,這裡是新增,不可重複讀是更改(或刪除)。這兩種情況對策是不一樣的,對於不可重複讀,只需要採取行級鎖防止該記錄資料被更改或刪除,然而對於幻讀必須加表級鎖個人認為也可能是GAP,防止在這個表中新增一條資料。

4. 第一類丟失更新updateA事務撤銷時,把已提交的B事務的資料覆蓋掉。

5. 第二類丟失更新(add/delete)A事務提交時,把已提交的B事務的資料覆蓋掉。

三級封鎖協議

1. 一級封鎖協議:事務T中如果對資料R有寫操作,必須在這個事務中對R的第一次讀操作前對它X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。

2. 二級封鎖協議:一級封鎖協議加上事務T在讀取資料R之前必須先對其S鎖讀完後方可釋放S鎖。 

3. 三級封鎖協議 :一級封鎖協議加上事務T在讀取資料R之前必須先對其S鎖,直到事務結束才釋放。

  可見,三級鎖操作一個比一個厲害(滿足高階鎖則一定滿足低階鎖)。但有個非常致命的地方,一級鎖協議就要在第一次讀加x鎖,直到事務結束。幾乎就要在整個事務加寫鎖了,效率非常低。三級封鎖協議只是一個理論上的東西,實際資料庫常用另一套方法來解決事務併發問題

  隔離性級別

mysql用意向鎖(另一種機制)來解決事務併發問題,為了區別封鎖協議,弄了一個新概念隔離性級別:包括Read Uncommitted、Read Committed、Repeatable Read、Serializable,見這篇mysql 一般預設Repeatable Read

   

  

  終於發現自己為什麼會誤會事務能解決丟失修改了。至於為什麼隔離性級別不解決丟失修改,我猜是有更好的解決方案吧。

  總結一下,repeatable read能解決髒讀和不可重複讀,但不嗯呢該解決丟失修改。

mysql的行鎖和表鎖

  說了那麼久,終於入正題了,先來說說什麼是行鎖和表鎖。

· 表級鎖:每次操作鎖住整張表。開銷小,加鎖快不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高併發度最低

· 行級鎖:每次操作鎖住一行資料。開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高;

· 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

1 MyISAM的鎖

  稍微提一下MyISAM,只說和InnoDB不同的。

a. MyISAM只有表鎖,鎖又分為讀鎖和寫鎖。 


b. 沒有事務不支援事務更準確,不用考慮併發問題,世界和平~

c. 由於鎖的粒度太大,所以當該表寫併發量較高時,要等待的查詢就會很多了。優化見這裡

2 InnoDB的行鎖和表鎖

  沒有特定的語法。mysql的行鎖是通過索引體現的,參考

如果where條件中只用到索引項,則加的是行鎖;否則加的是表鎖。比如說主鍵索引,唯一索引和聚簇索引等。如果sql的where是全表掃描的,想加行鎖也愛莫能助。

  行鎖和表鎖對我們程式設計有什麼影響,要在where中儘量只用索引項,否則就會觸發表鎖。另一個可能是,我們發瘋了地想優化查詢,但where子句中就是有非索引項,於是我們自己寫連線?

  行鎖和表鎖各適合怎麼樣的應用,待求證?。個人認為行鎖適合寫多讀少,表鎖適合讀多寫少。

3 讀鎖和寫鎖

InnoDB用意向鎖?實現隔離性級別,原理未名,貼張圖:


  回想鎖協議,對什麼操作加什麼鎖是一個問題,加鎖加到什麼時候是一個問題。鎖協議裡常常會看到“加鎖直到事務結束”的煩心字樣。而在InnoDB中,select,insert,update,delete等語句執行時都會自動加解鎖。select的鎖一般執行完就釋放了,修改操作的X鎖會持有到事務結束,效率高很多。至於詳細的加鎖原理,見這裡,搜“InnoDB儲存引擎中不同SQL在不同隔離級別下鎖比較”

mysql也給使用者提供了加鎖的機會,只要在sql後加LOCK IN SHARE MODE FOR UPDATE

共享鎖S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE排他鎖X):SELECT * FROM table_name WHERE ... FOR UPDATE

  值得注意的是,自己加的鎖沒有釋放鎖的語句,所以鎖會持有到事務結束

mysql 還提供了LOCK TABLES,UNLOCK TABLES,用於加表鎖,怎麼用還不太清楚?

4 考察加鎖的情況

  加了讀鎖還是寫鎖,加了行鎖還是表鎖,說什麼時候釋放,可以從原理上分析。但剛開始時我不太懂原理,於是又寫了個程式。

publicclass ForUpdate1  implements Runnable{

privateCountDownLatch countDown;

public ForUpdate1(CountDownLatch countDown){

this.countDown = countDown;

    }

    @Override

publicvoid run() {

        Connection conn=null;

try {

            Class.forName("com.mysql.jdbc.Driver");

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8",

                    "root", "123");

        } catch (Exception e) {

            e.printStackTrace();

return;

        }

try {

            conn.setAutoCommit(false);

/*PreparedStatement ps =conn.prepareStatement("select * from LostUpdate where id =1 for update");

            ps.executeQuery();*/

            PreparedStatement ps =conn.prepareStatement("update LostUpdate set count =1 where id =1");

            ps.executeUpdate();

            Thread.sleep(10000);

            conn.commit();

            System.out.println("test 1 finish");

            countDown.countDown();

        } catch (Exception e) {

try {

                conn.rollback();

            } catch (SQLException e1) {

                e1.printStackTrace();

            }

            e.printStackTrace();

        }    

    }

}

publicclass ForUpdate2  implements Runnable{

private CountDownLatch countDown;

public ForUpdate2(CountDownLatch countDown){

this.countDown = countDown;

    }

    @Override

publicvoid run() {

        Connection conn=null;

try {

            Class.forName("com.mysql.jdbc.Driver");

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8",

                    "root", "123");

        } catch (Exception e) {

            e.printStackTrace();

return;

        }

try {

            Thread.sleep(2000);

            conn.setAutoCommit(false);

            PreparedStatement ps =conn.prepareStatement("select * from LostUpdate where id =1 for update");

            ps.executeQuery();

/*PreparedStatement ps =conn.prepareStatement("update LostUpdate set count =1 where id =1");

            ps.executeUpdate();*/

            conn.commit();

            System.out.println("test 2 finish");

            countDown.countDown();

        } catch (Exception e) {

try {

                conn.rollback();

            } catch (SQLException e1) {

                e1.printStackTrace();

            }

            e.printStackTrace();

        }    

    }

}

publicclass TestForUpdate {    

publicstaticvoid main(String[] args) throws InterruptedException {

finalintTHREAD_COUNT=10;兩個執行緒

        ExecutorService threadPool=Executors.newFixedThreadPool(THREAD_COUNT);

        CountDownLatch count=new CountDownLatch(2);這裡覺得了是個執行緒

        //兩次提交

        threadPool.execute(new ForUpdate1(count));

        threadPool.execute(new ForUpdate2(count));

        threadPool.shutdown();

        count.await();

相關推薦

第二百九十節MySQL數據庫-MySQL命令導出導入數據庫數據庫備份還原

mman tro 建立 pro lena ont mysq 提示 sql數據庫 MySQL命令行導出導入數據庫,數據庫備份還原 MySQL命令行導出數據庫:1,進入MySQL目錄下的bin文件夾:cd MySQL中到bin文件夾的目錄如我輸入的命令行:cd C:\Pro

java Mysql的跨伺服器不同結構的聯合查詢兩不同伺服器上的不同查詢

由於業務的需要,需要從兩個資料庫中取得資料,伺服器A儲存有角色使用者配置表和角色表,伺服器B有使用者表和公司表。先不管架構為什麼要這樣設計資料表,因為確實有這樣的需求,但是這確實是一個令人頭疼的事情,但是還是要面帶微笑~ 解決思路一: 在資料庫中聯合不同資料庫的表中的DDL

20181022mysql操作一:建立庫的增刪改查資料的增刪改

1、建立資料庫 create database python charset=utf8; 2、使用資料庫 use python; 3、建立表結構 create table student(     id int primary key auto_increment

Sql批量建、刪名以數字命名且固定長度

由於專案需要,需要一次性建5000張表,且每張表的命名為Target0001-Target5000:程式碼如下:/*下面迴圈建立有誤,第一行 '4' 附近有錯誤*/ declare @ii int DECLARE @length INT set @ii = 1 SET @l

MySQL中的頁級

在電腦科學中,鎖是在執行多執行緒時用於強行限制資源訪問的同步機制,即用於在併發控制中保證對互斥要求的滿足。 在DBMS中,可以按照鎖的粒度把資料庫鎖分為行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )。 行級鎖 行級鎖是Mysql中鎖定粒度最細的一種

MySQL/InnoDB中樂觀悲觀、共享、排它、死概念的理解

MySQL/InnoDB的加鎖,一直是一個面試中常問的話題。例如,資料庫如果有高併發請求,如何保證資料完整性?產生死鎖問題如何排查並解決?我在工作過程中,也會經常用到,樂觀鎖,排它鎖,等。於是今天就對這幾個概念進行學習,屢屢思路,記錄一下。 注:MySQL是一

mysql/innoDB中樂觀悲觀共享排他概念的理解

MySQL是一個支援外掛式儲存引擎的資料庫系統。本文下面的所有介紹,都是基於InnoDB儲存引擎,其他引擎的表現,會有較大的區別。 儲存引擎檢視 MySQL給開發者提供了查詢儲存引擎的功能,我這裡使用的是MySQL5.5.28,可以使用: show engine

mysql--樂觀悲觀

一 引言--為什麼mysql提供了鎖  最近看到了mysql有行鎖和表鎖兩個概念,越想越疑惑。為什麼mysql要提供鎖機制,而且這種機制不是一個擺設,還有很多人在用。在現代資料庫裡幾乎有事務機制,acid的機制應該能解決併發排程的問題了,為什麼還要主動加鎖呢?  後來看到一篇

數據庫

開始 mode 由於 一個數 table 並且 增刪改 又能 對數 鎖主要用於多用戶環境下保證數據庫完整性和一致性。 我們知道,多個用戶能夠同時操縱同一個數據庫中的數據,會發生數據不一致現象。即如果沒有鎖定且多個用戶同時訪問一個數據庫,則當他們的事務同時使用相同

悲觀樂觀共享排他

悲觀鎖:   顧名思義,很悲觀,就是每次拿資料的時候都認為別的執行緒會修改資料,所以在每次拿的時候都會給資料上鎖。上鎖之後,當別的執行緒想要拿資料時,就會阻塞,直到給資料上鎖的執行緒將事務提交或者回滾。傳統的關係型資料庫裡就用到了很多這種鎖機制,比如行鎖,表鎖,共享鎖,排他鎖等,都是在做操作之前

悲觀樂觀排他----MYSQL

在說具體的鎖結構時,先思考一個問題,那就是為什麼要上鎖?然後我要如何選擇鎖?鎖具體如何實現? 在文章得末尾我給出了我的個人答案。 一、什麼是悲觀鎖?   1、悲觀鎖就是在操作資料時,認為此操作會出現資料衝突,所以在進行每次操作時都要通過獲取鎖才能進行對相同資料的操作,這點跟java中的synchronized

資料庫中的樂觀悲觀

樂觀鎖、悲觀鎖:http://blog.csdn.net/hongchangfirst/article/details/26004335 行鎖、表鎖:http://blog.sina.com.cn/s/blog_703074da0101ghsh.html

Mysql過程詳解(9)-innodb下的記錄間隙next-key

ans 唯一索引 crazy cimage -h insert tran 存在 gin Mysql加鎖過程詳解(1)-基本知識 Mysql加鎖過程詳解(2)-關於mysql 幻讀理解 Mysql加鎖過程詳解(3)-關於mysql 幻讀理解 Mysql加鎖過程詳解(4)-

MySQL-----、頁樂觀悲觀

數據庫操作 重要 讀寫 收購 線程並發 串行化 之前 並發執行 引擎 回顧: ACID:DBMS在寫入或更新資料的過程中,為保證事務正確可靠,具有的四個特性:原子性(不可分割性)、一致性、隔離性(獨立性)、持久性 一個事務:一系列數據庫操作組成的一個完整的邏輯過程 原子性:

Mysql MyISAM與InnoDB 以及分庫分優化

搜索 ash 能力 缺點 外部 加鎖 結構 create 主從 一、 兩種存儲引擎:MyISAM與InnoDB 區別與作用 1. count運算上的區別: 因為MyISAM緩存有表meta-data(行數等),因此在做COUNT(*)時對於一個結構很好的查詢是不需要消耗多少

mysql樂觀悲觀、共享、排它概念的理解

實驗環境: mysql5.6 儲存引擎:innoDB 我們在操作資料庫的時候,可能會由於併發問題而引起的資料的不一致性(資料衝突) 樂觀鎖 樂觀鎖不是資料庫自帶的,需要我們自己去實現。樂觀鎖是指操作資料庫時(更新操作),想法很樂觀,認為這次的操作不會導致衝突,在

MySQL樂觀悲觀、共享、排它的概念

樂觀鎖 樂觀鎖是指在操作資料庫時(更新操作),想法很樂觀,認為此次操作不會導致衝突,所以在操作資料時,不進行任何其他的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突。 悲觀鎖 悲觀鎖是指在操作資料庫時(更新操作),想法很悲觀,認為此次操作會出現衝突,所以在

MySQL詳解(、頁悲觀樂觀等)

原文地址:http://blog.csdn.net/mysteryhaohao/article/details/51669741 鎖,在現實生活中是為我們想要隱藏於外界所使用的一種工具。在計算機中,是協調多個程序或執行緒併發訪問某一資源的一種機制。在資料庫當中,除了傳統

MySQL詳解MyISAM與Innodb比較

鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制。在資料庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,資料也是一種供許多使用者共享的資源。如何保證資料併發訪問的一致性、有效性是所有資料庫必須解決的一個問題,鎖衝突也是影響資料庫併發訪問效能的一個重要因素。從這個角度來說,鎖對資料

mysql悲觀高併發

1.高併發的時候有2種處理 1)後端進行執行緒安全處理,synchrnoized,還有其他不同粒度的鎖 2)在資料庫設定鎖,當你讀的時候,不允許其他人修改。可以用mysql的悲觀鎖 2.悲觀鎖 select * from 表名 for update for update很重