只有光頭才能變強
索引和鎖在數據庫中能夠說是很是重要的知識點了,在面試中也會常常會被問到的。html
本文力求簡單講清每一個知識點,但願你們看完能有所收穫mysql
聲明:若是沒有說明具體的數據庫和存儲引擎, 默認指的是MySQL中的InnoDB存儲引擎
在以前,我對索引有如下的認知:git
INSERT/UPDATE/DELETE
操做就不要創建索引了,換言之:索引會下降插入、刪除、修改等維護任務的速度。看起來好像啥都知道,但面試讓你說的時候可能就GG了:程序員
首先Mysql的基本存儲結構是頁(記錄都存在頁裏邊):github
而每一個數據頁中的記錄又能夠組成一個單向鏈表面試
因此說,若是咱們寫select * from user where username = 'Java3y'
這樣沒有進行任何優化的sql語句,默認會這樣作:算法
定位到記錄所在的頁sql
從所在的頁內中查找相應的記錄數據庫
很明顯,在數據量很大的狀況下這樣查找會很慢!segmentfault
索引作了些什麼可讓咱們查詢加快速度呢?
其實就是將無序的數據變成有序(相對):
要找到id爲8的記錄簡要步驟:
很明顯的是:沒有用索引咱們是須要遍歷雙向鏈表來定位對應的頁,如今經過「目錄」就能夠很快地定位到對應的頁上了!
其實底層結構就是B+樹,B+樹做爲樹的一種實現,可以讓咱們很快地查找出對應的記錄。
參考資料:
B+樹是平衡樹的一種。
平衡樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。
若是一棵普通的樹在極端的狀況下,是能退化成鏈表的(樹的優勢就不復存在了)
B+樹是平衡樹的一種,是不會退化成鏈表的,樹的高度都是相對比較低的(基本符合矮矮胖胖(均衡)的結構)【這樣一來咱們檢索的時間複雜度就是O(logn)】!從上一節的圖咱們也能夠看見,創建索引實際上就是創建一顆B+樹。
B+樹刪除和修改具體可參考:
除了B+樹以外,還有一種常見的是哈希索引。
哈希索引就是採用必定的哈希算法,把鍵值換算成新的哈希值,檢索時不須要相似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法便可馬上定位到相應的位置,速度很是快。
看起來哈希索引很牛逼啊,但其實哈希索引有好幾個侷限(根據他本質的原理可得):
參考資料:
主流的仍是使用B+樹索引比較多,對於哈希索引,InnoDB是自適應哈希索引的(hash索引的建立由InnoDB存儲引擎引擎自動優化建立,咱們干預不了)!
參考資料:
簡單歸納:
區別:
非彙集索引也叫作二級索引,不用糾結那麼多名詞,將其等價就好了~
非彙集索引在創建的時候也未必是單列的,能夠多個列來建立索引。
在建立多列索引中也涉及到了一種特殊的索引-->覆蓋索引
好比說:
(username,age)
,在查詢數據的時候:select username , age from user where username = 'Java3y' and age = 20
。最左匹配原則:
(a)
,也能夠複雜如多個列(a, b, c, d)
,即聯合索引。(>、<、between、like
左匹配)等就不能進一步匹配了,後續退化爲線性查找。例子:
(a, b, c, d)
,查詢條件a = 1 and b = 2 and c > 3 and d = 4
,則會在每一個節點依次命中a、b、c,沒法命中d。(c已是範圍查詢了,d確定是排不了序了)爲何能命中c?
舉個簡單例子:select * from user where age >30;
若是在age列建立索引,那你說會走索引嗎?
不須要考慮=、in等的順序,mysql會自動優化這些條件的順序,以匹配儘量多的索引列。
例子:
(a, b, c, d)
,查詢條件c > 3 and b = 2 and a = 1 and d < 4
與a = 1 and c > 3 and b = 2 and d < 4
等順序都是能夠的,MySQL會自動優化爲a = 1 and b = 2 and c > 3 and d < 4
,依次命中a、b、c。索引在數據庫中是一個很是重要的知識點!上面談的其實就是索引最基本的東西,要建立出好的索引要顧及到不少的方面:
(>,<,BETWEEN,LIKE)
就中止匹配。COUNT(DISTINCT col) / COUNT(*)
。表示字段不重複的比率,比率越大咱們掃描的記錄數就越少。FROM_UNIXTIME(create_time) = '2016-06-06'
就不能使用索引,緣由很簡單,B+樹中存儲的都是數據表中的字段值,可是進行檢索時,須要把全部元素都應用函數才能比較,顯然這樣的代價太大。因此語句要寫成 : create_time = UNIX_TIMESTAMP('2016-06-06')
。6,單個多列組合索引和多個單列索引的檢索查詢效果不一樣,由於在執行SQL時,MySQL只能使用一個索引,會從多個單列索引中選擇一個限制最爲嚴格的索引(經指正,在MySQL5.0之後的版本中,有「合併索引」的策略,翻看了《高性能MySQL 第三版》,書做者認爲:仍是應該創建起比較好的索引,而不該該依賴於「合併索引」這麼一個策略)。
參考資料:
MySQL學習之——索引(普通索引、惟一索引、全文索引、索引匹配原則、索引命中等)
在mysql中的鎖看起來是很複雜的,由於有一大堆的東西和名詞:排它鎖,共享鎖,表鎖,頁鎖,間隙鎖,意向排它鎖,意向共享鎖,行鎖,讀鎖,寫鎖,樂觀鎖,悲觀鎖,死鎖。這些名詞有的博客又直接寫鎖的英文的簡寫--->X鎖,S鎖,IS鎖,IX鎖,MMVC...
鎖的相關知識又跟存儲引擎,索引,事務的隔離級別都是關聯的....
這就給初學數據庫鎖的人帶來很多的麻煩~~~因而我下面就簡單整理一下數據庫鎖的知識點,但願你們看完會有所幫助。
很多人在開發的時候,應該不多會注意到這些鎖的問題,也不多會給程序加鎖(除了庫存這些對數量準確性要求極高的狀況下)
通常也就聽過常說的樂觀鎖和悲觀鎖,瞭解過基本的含義以後就沒了~~~
定心丸:即便咱們不會這些鎖知識,咱們的程序在通常狀況下仍是能夠跑得好好的。由於這些鎖數據庫隱式幫咱們加了
UPDATE、DELETE、INSERT
語句,InnoDB會自動給涉及數據集加排他鎖(X)SELECT
前,會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、DELETE、INSERT
等)前,會自動給涉及的表加寫鎖,這個過程並不須要用戶干預 只會在某些特定的場景下才須要手動加鎖,學習數據庫鎖知識就是爲了:
首先,從鎖的粒度,咱們能夠分紅兩大類:
表鎖
行鎖
不一樣的存儲引擎支持的鎖粒度是不同的:
InnoDB只有經過索引條件檢索數據才使用行級鎖,不然,InnoDB將使用表鎖
表鎖下又分爲兩種模式:
從下圖能夠清晰看到,在表讀鎖和表寫鎖的環境下:讀讀不阻塞,讀寫阻塞,寫寫阻塞!
從上面已經看到了:讀鎖和寫鎖是互斥的,讀寫操做是串行。
max_write_lock_count
和low-priority-updates
值得注意的是:
The LOCAL modifier enables nonconflicting INSERT statements (concurrent inserts) by other sessions to execute while the lock is held. (See Section 8.11.3, 「Concurrent Inserts」.) However, READ LOCAL cannot be used if you are going to manipulate the database using processes external to the server while you hold the lock. For InnoDB tables, READ LOCAL is the same as READ
concurrent_insert
來指定哪一種模式,在MyISAM中它默認是:若是MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM容許在一個進程讀表的同時,另外一個進程從表尾插入記錄。參考資料:
上邊簡單講解了表鎖的相關知識,咱們使用Mysql通常是使用InnoDB存儲引擎的。InnoDB和MyISAM有兩個本質的區別:
從上面也說了:咱們是不多手動加表鎖的。表鎖對咱們程序員來講幾乎是透明的,即便InnoDB不走索引,加的表鎖也是自動的!
咱們應該更加關注行鎖的內容,由於InnoDB一大特性就是支持行鎖!
InnoDB實現瞭如下兩種類型的行鎖。
共享鎖(S鎖):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。
排他鎖(X鎖):容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。
看完上面的有沒有發現,在一開始所說的:X鎖,S鎖,讀鎖,寫鎖,共享鎖,排它鎖其實總共就兩個鎖,只不過它們有多個名字罷了~~~
Intention locks do not block anything except full table requests (for example, LOCK TABLES ... WRITE). The main purpose of intention locks is to show that someone is locking a row, or going to lock a row in the table.
另外,爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:
參考資料:
數據庫事務有不一樣的隔離級別,不一樣的隔離級別對鎖的使用是不一樣的,鎖的應用最終致使不一樣事務的隔離級別
MVCC(Multi-Version Concurrency Control)多版本併發控制,能夠簡單地認爲:MVCC就是行級鎖的一個變種(升級版)。
在表鎖中咱們讀寫是阻塞的,基於提高併發性能的考慮,MVCC通常讀寫是不阻塞的(因此說MVCC不少狀況下避免了加鎖的操做)
快照有兩個級別:
語句級
Read committed
隔離級別事務級別
Repeatable read
隔離級別咱們在初學的時候已經知道,事務的隔離級別有4種:
Read uncommitted
Read committed
Repeatable read
Serializable
Read uncommitted
會出現的現象--->髒讀:一個事務讀取到另一個事務未提交的數據
Read uncommitted
過程:
Read committed
避免髒讀的作法其實很簡單:
Read committed
過程:
但Read committed
出現的現象--->不可重複讀:一個事務讀取到另一個事務已經提交的數據,也就是說一個事務能夠看到其餘事務所作的修改
上面也說了,Read committed
是語句級別的快照!每次讀取的都是當前最新的版本!
Repeatable read
避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即便被修改了,也只會讀取當前事務版本的數據。
呃...若是仍是不太清楚,咱們來看看InnoDB的MVCC是怎麼樣的吧(摘抄《高性能MySQL》)
至於虛讀(幻讀):是指在一個事務內讀取到了別的事務插入的數據,致使先後讀取不一致。
Repeatable read
隔離級別加上GAP間隙鎖已經處理了幻讀了。參考資料:
擴展閱讀:
不管是Read committed
仍是Repeatable read
隔離級別,都是爲了解決讀寫衝突的問題。
單純在Repeatable read
隔離級別下咱們來考慮一個問題:
此時,用戶李四的操做就丟失掉了:
(ps:暫時沒有想到比較好的例子來講明更新丟失的問題,雖然上面的例子也是更新丟失,但必定程度上是可接受的..不知道有沒有人能想到不可接受的更新丟失例子呢...)
解決的方法:
- 樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,須要再次查看該字段的值是否和第一次的同樣。若是同樣更新,反之拒絕。之因此叫樂觀,由於這個模式沒有從數據庫加鎖,等到更新的時候再判斷是否能夠更新。
- 悲觀鎖是數據庫層面加鎖,都會阻塞去等待鎖。
因此,按照上面的例子。咱們使用悲觀鎖的話其實很簡單(手動加行鎖就好了):
select * from xxxx for update
在select 語句後邊加了 for update
至關於加了排它鎖(寫鎖),加了寫鎖之後,其餘的事務就不能對它修改了!須要等待當前事務修改完以後才能夠修改.
select ... for update
,李四就沒法對該條記錄修改了~樂觀鎖不是數據庫層面上的鎖,是須要本身手動去加的鎖。通常咱們添加一個版本字段來實現:
具體過程是這樣的:
張三select * from table
--->會查詢出記錄出來,同時會有一個version字段
李四select * from table
--->會查詢出記錄出來,同時會有一個version字段
李四對這條記錄作修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}
,判斷以前查詢到的version與如今的數據的version進行比較,同時會更新version字段
此時數據庫記錄以下:
張三也對這條記錄修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}
,但失敗了!由於當前數據庫中的版本跟查詢出來的版本不一致!
參考資料:
當咱們用範圍條件檢索數據而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合範圍條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」。InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖。
值得注意的是:間隙鎖只會在Repeatable read
隔離級別下使用~
例子:假如emp表中只有101條記錄,其empid的值分別是1,2,...,100,101
Select * from emp where empid > 100 for update;
上面是一個範圍查詢,InnoDB不只會對符合條件的empid值爲101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的「間隙」加鎖。
InnoDB使用間隙鎖的目的有兩個:
Repeatable read
隔離級別下再經過GAP鎖便可避免了幻讀)知足恢復和複製的須要
併發的問題就少不了死鎖,在MySQL中一樣會存在死鎖的問題。
但通常來講MySQL經過回滾幫咱們解決了很多死鎖的問題了,但死鎖是沒法徹底避免的,能夠經過如下的經驗參考,來儘量少遇到死鎖:
參考資料:
上面說了一大堆關於MySQL數據庫鎖的東西,如今來簡單總結一下。
表鎖其實咱們程序員是不多關心它的:
如今咱們大多數使用MySQL都是使用InnoDB,InnoDB支持行鎖:
在默認的狀況下,select
是不加任何行鎖的~事務能夠經過如下語句顯示給記錄集加共享鎖或排他鎖。
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
。SELECT * FROM table_name WHERE ... FOR UPDATE
。InnoDB基於行鎖還實現了MVCC多版本併發控制,MVCC在隔離級別下的Read committed
和Repeatable read
下工做。MVCC可以實現讀寫不阻塞!
InnoDB實現的Repeatable read
隔離級別配合GAP間隙鎖已經避免了幻讀!
參考資料:
本文主要介紹了數據庫中的兩個比較重要的知識點:索引和鎖。他倆能夠說息息相關的,鎖會涉及到不少關於索引的知識~
我我的比較重視對總體知識點的把控,一些細節的地方可能就沒有去編寫了。在每個知識點下都會有不少的內容,有興趣的同窗能夠在我給出的連接中繼續閱讀學習。固然了,若是有比較好的文章和資料也不妨在評論區分享一下哈~
我只是在學習的過程當中,把本身遇到的問題寫出來,整理出來,但願能夠對你們有幫助。若是文章有錯的地方,但願你們能夠在評論區指正,一塊兒學習交流~
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠 關注微信公衆號:Java3y。爲了你們方便,剛新建了一下 qq羣:742919422,你們也能夠去交流交流。謝謝支持了!但願能多介紹給其餘有須要的朋友
文章的目錄導航: