總結一下本身多年來對MySQL的相關知識,作個梳理。mysql
本文用到的MySQL版本:5.7.22sql
咱們開的的各式各樣系統中,系統運行須要CPU、內存、I/O、磁盤等等資源。但除了硬資源外,還有最爲重要的軟資源:數據。數據庫
當人們訪問操做咱們的系統時,其實歸根是對數據的查看與生產。那麼對於同一份數據,若是多個用戶同時對它查看、修改時會出現什麼問題呢?這必然會帶來競爭,而爲了控制併發的讀取、修改數據會對數據形成的不一致、錯亂等問題,數據庫引入了鎖的機制。併發
固然鎖的問題解決了併發訪問數據的問題,而不可避免的會對系統的性能產生負面影響。總結一下各類鎖的使用場景方便在實踐中更好的運用它從而提高系統性能。性能
咱們平常開發中用到最多的存儲引擎是Innodb 與 MyISAM兩種,而 Innodb 如今更可能是首選,所以主要是對 Innodb 的說明,MyISAM 跟可能是做爲一個對比的角色。
MySQL的鎖能夠按照多種方式進行劃分,這裏使用最經常使用的兩種方式進行劃分:粒度與使用方式。測試
須要特別說明的是:樂觀鎖與悲觀鎖並非數據庫中實現的鎖機制,是須要咱們本身去實現的。它是一種思想,咱們不只僅能夠用在MySQL中,其它地方有須要的也能夠用到。而像悲觀鎖它也是利用數據庫現有的機制進行實現的。下面先根據不一樣分類對說明相關概念。spa
樂觀鎖 機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。相對悲觀鎖而言,樂觀鎖更傾向於開發運用。設計
悲觀鎖 具備強烈的獨佔和排他特性。它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。code
表級鎖 是MySQL中鎖定粒度最大的一種鎖,表示對當前操做的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MyISAM與InnoDB都支持表級鎖定。表級鎖分爲表共享讀鎖與表獨佔寫鎖。blog
行級鎖 是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操做的行進行加鎖。行級鎖能大大減小數據庫操做的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共享鎖 和 排他鎖。
頁級鎖 是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。因此取了折衷的頁級,一次鎖定相鄰的一組記錄。BDB支持頁級鎖。
這裏須要說明的是,悲觀鎖是一種思想,它的實現是使用了 共享鎖與排他鎖來實現的。所以悲觀鎖自己並非MySQL實現的鎖機制,它是咱們造出來的一個概念。
另外,我看到不少文章在講悲觀鎖時,只說排他鎖是悲觀鎖機制,沒有說共享鎖是什麼機制,而我認爲共享鎖也屬於悲觀鎖,具體緣由日後看。
MyISAM 相關的鎖機制我就略過不總結了。
InnoDB 實現了兩種類型的行鎖,共享鎖(S)與排他鎖(X)。而後因爲 InnoDB引擎又支持表級鎖,因此它內部又有意向共享鎖(IS)與意向排他鎖(IX)。這兩種表鎖,都是InnoDB內部自動處理,換句話說咱們寫代碼是沒法控制也不須要控制的。咱們能控制的是S與X鎖。
在平常操做中,UPDATE、INSERT、DELETE InnoDB會自動給涉及的數據集加排他鎖,通常的 SELECT 通常是不加任何鎖的。咱們可使用如下方式顯示的爲 SELECT 加鎖。
那麼何時該用共享鎖何時用排他鎖呢?
通常來說,共享鎖主要用在須要數據依存關係時來確認某行記錄是否存在,並確保沒有人對這個記錄進行UPDATE或者DELETE操做(包括加鎖的事物也只能讀)。簡單說就是你們均可以讀數據,可是沒法修改(更新或者刪除),所以我認爲::共享鎖也是悲觀鎖::的一種實現。
排他鎖是與共享鎖相對應,自身加排他鎖的事物可以本身發起修改操做,其它事物沒法再對該數據加共享或者排他鎖。
這裏須要注意上面說到的一點,因爲InnoDB引擎是行鎖,無論咱們在這條數據上加了共享鎖仍是排他鎖,簡單的select語句依然可使用的,由於默認在InnoDB中select是不加鎖的。
這裏還有一點,上圖中咱們寫到一個 間隙鎖,這是什麼東西?好比:當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。它存在的主要目的有一個是爲了解決幻讀問題,由於RR做爲InnoDB的默認事物隔離級別,是存在幻讀問題的,而咱們在實際操做中確沒有出現,就是由於這裏作了處理。
關於樂觀鎖是如何加鎖的,這個不一樣系統有不一樣的實現,簡單來講,對每個數據維護一個版本號,每次讀取時把版本號讀取出來,更新時版本號+1。而後更新時將讀取的版本號做爲條件,若是有其它事物更新了,那麼必然會致使版本號變化,由於本次更新不會成功。這種機制最大程度的保證了併發。
mysql> show status like 'innodb_row_lock%'; +-------------------------------+--------+ | Variable_name | Value | +-------------------------------+--------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 218276 | | Innodb_row_lock_time_avg | 18189 | | Innodb_row_lock_time_max | 51058 | | Innodb_row_lock_waits | 12 | +-------------------------------+--------+ 5 rows in set (0.05 sec)
上面的語句可以展現當前系統鎖的狀況,當系統鎖爭用比較嚴重的時候,Innodb_row_lock_waits
和 Innodb_row_lock_time_avg
的值會比較高。上面的數據是因爲我作實驗致使的。你們能夠檢查下本身的系統。
咱們經常說InnoDB是行鎖,可是這裏介紹一下它鎖表的狀況。
InnoDB行鎖是經過索引上的索引項來實現的,這一點MySQL與Oracle不一樣,後者是經過在數據中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特色意味者:只有經過索引條件檢索數據,InnoDB纔會使用行級鎖,不然,InnoDB將使用表鎖!
在實際應用中,要特別注意InnoDB行鎖的這一特性,否則的話,可能致使大量的鎖衝突,從而影響併發性能。
這裏咱們實際演示一下,一來讓你們瞭解如何測試鎖狀況,二來這樣用例子也許更容易說明問題。表結構以下:
Create Table: CREATE TABLE `tb_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '', `age` mediumint(9) NOT NULL DEFAULT '1', `money` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
咱們看當where條件不是索引時,若是加了排他鎖,對這個表其它行記錄也不能再加排他鎖了,這明顯就是鎖住了整個表。而若是條件是索引字段,則它只會對where條件指定的行數據加鎖,另外一個事務能夠對其它行數據加鎖。
有些文章說只有表沒有索引纔會鎖表,經過上面的實驗,我以爲是不許確的。