1. 程式人生 > 其它 >mysql 行鎖_MySQL InnoDB引擎的表鎖及行鎖

mysql 行鎖_MySQL InnoDB引擎的表鎖及行鎖

技術標籤:mysql 行鎖

043fc57142a53ee5173b6fe47e86180a.png

今天跟大家分享下MySQL InnoDB引擎的表鎖及行鎖的知識。

0前言

InnoDB與MyISAM的最大不同有兩點:一是支援事務(TRANSACTION);二是採用了行級鎖。行級鎖與表級鎖本來就有許多不同之處,另外,事務的引入也帶來了一些新問題。

在現代資料庫裡幾乎有事務機制,acid的機制應該能解決併發排程的問題了,為什麼還要主動加鎖呢?原因是防止更新丟失,並不能單靠資料庫事務控制器來解決,需要應用程式對要更新的資料加必要的鎖來解決。

1行鎖和表鎖

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

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

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

1、MyISAM的鎖

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

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

63a07d1c5cd66eb0898a1ed651831fb4.png

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

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

2、InnoDB的行鎖和表鎖

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

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

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

3、讀鎖和寫鎖

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

279ab648b961d4830ce57df93a08c684.png

回想鎖協議,對什麼操作加什麼鎖是一個問題,加鎖加到什麼時候有是一個問題。鎖協議裡常常會看到“加鎖直到事務結束”的煩心字樣。而在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、考察加鎖的情況

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

  1. public class ForUpdate1  implements Runnable{private CountDownLatch countDown;public ForUpdate1(CountDownLatch countDown){this.countDown = countDown;}@Overridepublic void 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();}}}
  1. public class ForUpdate2  implements Runnable{private CountDownLatch countDown;public ForUpdate2(CountDownLatch countDown){this.countDown = countDown;}@Overridepublic void 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();}}}
  1. public class TestForUpdate {public static void main(String[] args) throws InterruptedException {final int THREAD_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();System.out.println("finish");}}

只有兩個執行緒,ForUpdate1先執行sql語句之後等10s,ForUpdate2先等待2s再執行sql語句。所以如果ForUpdate1持有鎖,而且ForUpdate2等待,輸出就應該是test 1 finish->test 2 finish->finish;否則就是test 2 finish->test 1 finish->finish。

這個程式改一下能測試上面說的理論:

repeatable read能解決髒讀和不可重複讀比如行鎖真的只鎖住一行s,x,is和ix的關係

判斷加鎖情況,可以通過檢查InnoDB_row_lock狀態變數來分析系統上的行鎖的爭奪情況:

mysql> show status like 'innodb_row_lock%';

b7e3c9b04d26550c77eb52dda696cfa7.png

如果發現鎖爭用比較嚴重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比較高,還可以通過設定InnoDB Monitors來進一步觀察發生鎖衝突的表、資料行等,並分析鎖爭用的原因。

總結一下這一章,mysql提供了行鎖和表鎖,我們寫語句時應該儘量啟動行鎖,以提高效率;另一方面,也說了一下讀鎖和寫鎖的原理。好了武器(原理)我們都懂了,那就看怎麼優化了。

今天就分享這麼多,如果覺得文章對你有幫助,請點右下角【在看】,讓更多人看到該文章。

884e36842d10e8a393f5e1f53c245519.gif

近期熱門推薦?

1.遲到的年終總結,我的 2019,很拼~

2.程式設計師被質疑跳槽頻繁不穩定,隨後的一番話令HR啞口無言!

3.哎!又要過年了,程式設計師最怕問到什麼?

4.牛X,試用了下 GitHub 上 2 萬 Star 的第一搶票神器,3 秒鐘搶到!

5.2018年所有精華文章彙總,錯過了血虧!

關注公眾號

每天進步一點點

1b1a1927bfbbc1997cbec07c07366eab.png

點贊是最大的支援3e1391f2a8643d801b260f7769f173ae.gif