當面試官問到:《MySQL數據庫的鎖機制》該如何拿分?

當面試官問到:《MySQL數據庫的鎖機制》該如何拿分?

爲何要加鎖?java

問題背景mysql

  • 當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。

要解決的問題面試

  • 多用戶環境下保證數據庫完整性和一致性

鎖是什麼sql

  • 在計算機科學中,鎖是在執行多線程時用於強行限制資源訪問的同步機制,即用於在併發控制中保證對互斥要求的知足。
  • 加鎖是實現數據庫併發控制的一個很是重要的技術。當事務在對某個數據對象進行操做前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了必定的控制,在該事務釋放鎖以前,其餘的事務不能對此數據對象進行更新操做。

鎖的分類數據庫

bash

  • 行級鎖
  • 行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操做的行進行加鎖。行級鎖能大大減小數據庫操做的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共享鎖 和 排他鎖。
  • 特色
  • 開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。

微信

  • 表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操做的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MYISAM與INNODB都支持表級鎖定。表級鎖定分爲表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)。
  • 特色
  • 開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的機率最高,併發度最低。

多線程

  • 頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。因此取了折衷的頁級,一次鎖定相鄰的一組記錄。BDB支持頁級鎖
  • 特色
  • 開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常

MySQL經常使用存儲引擎的鎖機制架構

MyISAM和MEMORY採用表級鎖(table-level locking)併發

BDB採用頁面鎖(page-level locking)或表級鎖,默認爲頁面鎖

InnoDB支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖

  • InnoDB行鎖是經過給索引上的索引項加鎖來實現的,InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!

在實際應用中,要特別注意InnoDB行鎖的這一特性,否則的話,可能致使大量的鎖衝突,從而影響併發性能。

行級鎖都是基於索引的,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖。行級鎖的缺點是:因爲須要請求大量的鎖資源,因此速度慢,內存消耗大。

實例說明

  • MySQL InnoDB引擎默認的修改數據語句:update,delete,insert都會自動給涉及到的數據加上排他鎖。

select語句默認不會加任何鎖類型,若是加排他鎖可使用select …for update語句,加共享鎖可使用select … lock in share mode語句。

因此加過排他鎖的數據行在其餘事務種是不能修改數據的,也不能經過for update和lock in share mode鎖的方式查詢數據,但能夠直接經過select …from…查詢數據,由於普通查詢沒有任何鎖機制。

行級鎖與死鎖

MyISAM中是不會產生死鎖的,由於MyISAM老是一次性得到所需的所有鎖,要麼所有知足,要麼所有等待。而在InnoDB中,鎖是逐步得到的,就形成了死鎖的可能。

在MySQL中,行級鎖並非直接鎖記錄,而是鎖索引。索引分爲主鍵索引和非主鍵索引兩種,若是一條sql語句操做了主鍵索引,MySQL就會鎖定這條主鍵索引;若是一條語句操做了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。在UPDATE、DELETE操做時,MySQL不只鎖定WHERE條件掃描過的全部索引記錄,並且會鎖定相鄰的鍵值,即所謂的next-key locking。

當兩個事務同時執行,一個鎖住了主鍵索引,在等待其餘相關索引。另外一個鎖定了非主鍵索引,在等待主鍵索引。這樣就會發生死鎖。

發生死鎖後,InnoDB通常均可以檢測到,並使一個事務釋放鎖回退,另外一個獲取鎖完成事務。

共享鎖與排它鎖

共享鎖(Share Lock)

  • 共享鎖又稱讀鎖,是讀取操做建立的鎖。其餘用戶能夠併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放全部共享鎖。

若是事務T對數據A加上共享鎖後,則其餘事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。

用法 SELECT ... LOCK IN SHARE MODE;

在查詢語句後面增長LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請共享鎖,不然會被阻塞。其餘線程也能夠讀取使用了共享鎖的表,並且這些線程讀取的是同一個版本的數據。

排它鎖(eXclusive Lock)

  • 排他鎖又稱寫鎖,若是事務T對數據A加上排他鎖後,則其餘事務不能再對A加任任何類型的封鎖。獲准排他鎖的事務既能讀數據,又能修改數據。

用法 SELECT ... FOR UPDATE;

在查詢語句後面增長FOR UPDATE,Mysql會對查詢結果中的每行都加排他鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請排他鎖,不然會被阻塞。

樂觀鎖(Optimistic Lock)

是什麼

  • 假設認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作。

相對於悲觀鎖,在對數據庫進行處理的時候,樂觀鎖並不會使用數據庫提供的鎖機制。通常的實現樂觀鎖的方式就是記錄數據版本。

數據版本,爲數據增長的一個版本標識。當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當咱們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對,若是數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,不然認爲是過時數據。

實現數據版本有兩種方式,第一種是使用版本號,第二種是使用時間戳。

使用版本號實現樂觀鎖

  • 使用版本號時,能夠在數據初始化時指定一個版本號,每次對數據的更新操做都對版本號執行+1操做。並判斷當前版本號是否是該數據的最新的版本號。
1.查詢出商品信息select (status,status,version) from t_goods where id=#{id}2.根據商品信息生成訂單3.修改商品status爲2update t_goodsset status=2,version=version+1where id=#{id} and version=#{version};複製代碼

優勢與不足

  • 樂觀併發控制相信事務之間的數據競爭(data race)的機率是比較小的,所以儘量作下去,直到提交的時候纔去鎖定,因此不會產生任何鎖和死鎖。但若是直接簡單這麼作,仍是有可能會遇到不可預期的結果,例如兩個事務都讀取了數據庫的某一行,通過修改之後寫回數據庫,這時就遇到了問題

悲觀鎖(Pessimistic Lock)

是什麼

  • 在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制 (也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)

悲觀鎖的流程

  • 在對任意記錄進行修改前,先嚐試爲該記錄加上排他鎖(exclusive locking)。
  • 若是加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者拋出異常。具體響應方式由開發者根據實際須要決定。
  • 若是成功加鎖,那麼就能夠對記錄作修改,事務完成後就會解鎖了。
  • 其間若是有其餘對該記錄作修改或加排他鎖的操做,都會等待咱們解鎖或直接拋出異常。

MySQL InnoDB中使用悲觀鎖

  • 要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。set autocommit=0;
//0.開始事務begin;//1.查詢出商品信息select status from t_goods where id=1 for update;//2.根據商品信息生成訂單insert into t_orders (id,goods_id) values (null,1);//3.修改商品status爲2update t_goods set status=2;//4.提交事務commit;複製代碼

上面的查詢語句中,咱們使用了select…for update的方式,這樣就經過開啓排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id爲1的 那條數據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。這樣咱們能夠保證當前的數據不會被其它事務修改。

Java的鎖機制

線程的同步問題

  • 一段synchronized的代碼被一個線程執行以前,他要先拿到執行這段代碼的權限,在java裏邊就是拿到某個同步對象的鎖(一個對象只有一把鎖);若是這個時候同步對象的鎖被其餘線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池等待隊列中)。取到鎖後,他就開始執行同步代碼(被synchronized修飾的代碼);線程執行完同步代碼後立刻就把鎖還給同步對象,其餘在鎖池中等待的某個線程就能夠拿到鎖執行同步代碼了。這樣就保證了同步代碼在統一時刻只有一個線程在執行。

線程的同步方法:

  • 1. 在須要同步的方法的方法簽名中加入synchronized關鍵字。
  • 2. 使用synchronized塊對須要進行同步的代碼段進行同步。
  • 3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock對象。

ThreadLocal

當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本。使用ThreadLocal的典型場景如數據庫鏈接管理,線程會話管理等場景,只適用於獨立變量副本的狀況,若是變量爲全局共享的,則不適用在高併發下使用。

獲取資料:

最後給你們分享一些學習資料,裏面包括:(BATJ面試資料、高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)和Java進階學習路線圖。

領取方式:加微信號 weixin99ting 備註 :(資料) 便可獲取。

最後,祝你們早日學有所成!

相關文章
相關標籤/搜索