-
B+樹
B+樹是InnoDB底層采用的數(shù)據(jù)結(jié)構(gòu),它是一種多叉平衡搜索樹。InnoDB底層采用的存儲結(jié)構(gòu)就是B+樹。
它的優(yōu)點:-
高度比較低,減少IO的次數(shù)
因為B+樹采用是多叉樹,所以在節(jié)點相同的情況下,比正常的二叉樹的高度低了很多。
對于數(shù)據(jù)庫來說,最耗時的操作就是從磁盤加載數(shù)據(jù),因此想要提高數(shù)據(jù)庫的效率,必須盡量減少從磁盤加載數(shù)據(jù)的次數(shù)。而對于多叉樹,父子節(jié)點往往相距比較遠(yuǎn),因此在數(shù)據(jù)量比較大的時候,一次加載到父子節(jié)點幾乎是不可能的,最好的情況就是一次加載一個節(jié)點的數(shù)據(jù),然后找到下一個節(jié)點進(jìn)行加載,直到最后找到對應(yīng)的信息。
從磁盤加載信息的時候,最耗時的操作是尋道,而從磁盤讀取數(shù)據(jù)到數(shù)據(jù)總線的耗時幾乎可以忽略,所以磁盤讀取數(shù)據(jù)的時候會有一個預(yù)讀的過程。也就是說,盡管你只要磁盤某個地方的一小段信息,加載的時候也會讀取滿一整個頁的信息。所以,一個節(jié)點的大小設(shè)置成一個頁大小是最合適的,一次io可以加載進(jìn)一個節(jié)點的所有信息,然后再決定下一個節(jié)點。
B+樹 葉子節(jié)點有兄弟指針,方便遍歷
有些情況下,會出現(xiàn)需要全表掃描,因為B+樹的所有信息都存在葉子節(jié)點,所以只要找到第一個葉子節(jié)點,然后依次根據(jù)兄弟節(jié)點指針找到兄弟節(jié)點,最后就完成了整個數(shù)據(jù)庫的遍歷。
而B樹等需要前序遍歷這種操作,在內(nèi)存中倒是沒有差別,但是如果是在磁盤中,因為父子節(jié)點往往不在一個頁中,遍歷就涉及到非常多的磁盤IO,效率很低。
-
索引
InnoDB使用的是聚簇索引表,即一棵以主鍵為索引、行數(shù)據(jù)存儲在子節(jié)點上的B+樹。如果沒有主鍵,系統(tǒng)先找一個not null 且unique的列作為主鍵,如果沒有,就會默認(rèn)創(chuàng)建一個隱藏的主鍵作為索引。
其他的索引都是一個獨立的結(jié)構(gòu),利用這個結(jié)構(gòu)找到對應(yīng)的主鍵,即通過其他索引找到數(shù)據(jù)需要先到對應(yīng)的索引樹找到主鍵,然后再到主鍵的索引樹上找到數(shù)據(jù)。
如果where條件中的鍵值沒有索引,會掃描全表。-
鎖
mysql中的鎖有很多種:- 讀寫鎖
讀寫鎖是最常見的兩種鎖。讀鎖也叫共享鎖,允許其他人再獲得讀鎖,可以同時讀,但是不能更改數(shù)據(jù)。寫鎖也叫排他鎖,不允許其他人對數(shù)據(jù)進(jìn)行讀或者寫。 - Record Lock
Record鎖是一種索引鎖,它加在索引上防止被多次引用 - Gap Lock
Gap鎖的出現(xiàn)主要是用來解決幻讀,鎖住一個區(qū)間,沒有拿到這個區(qū)間的Gap Lock,就不能向這個區(qū)間插入。 - NextKey Lock
NextKey鎖其實就是Record Lock和Gap Lock的組合體,同時鎖住一個范圍和其中的記錄 - 意向鎖
意向所用來協(xié)調(diào)表鎖和行鎖。如果一個表有行排他鎖存在,那么想對表添加排他鎖,就一定要等這個行鎖釋放。但是想判斷表中是否存在行鎖只能遍歷,消耗太大。為了解決這個問題,產(chǎn)生了意向鎖。
當(dāng)獲取一個行鎖時,會先拿到一個表的意向鎖,這樣只要判斷一個表是否有意向鎖,就可以判斷是否可以添加表鎖了。
- 讀寫鎖
-
事務(wù)隔離級別
- read-uncommited
在這個級別下,讀寫不會加任何鎖,所以即使事務(wù)沒有提交,互相之間也能讀到彼此的更改。因此可能讀到對方更改一半的臟數(shù)據(jù)(臟讀)。 - read-commited(RC)
在這個級別下,寫的時候會加寫鎖,即在一個事務(wù)在更改一個數(shù)據(jù)的時候,其他事務(wù)無法獲取這個數(shù)據(jù),直到這個事務(wù)釋放寫鎖之后。但是如果在那個事務(wù)更改之前讀了一次,更改之后又讀了一次,這兩次的結(jié)果會不一致(不可重復(fù)讀)。因此InnoDB采用了快照讀解決這個問題,即在每行數(shù)據(jù)添加一個隱藏字段,最新更改的事務(wù)id,如果當(dāng)前的事務(wù)id小于這個值, 就回去undo log中找到本事務(wù)對應(yīng)的值,從而保證事務(wù)的一致性。
但是如果其他事務(wù)插入了一條數(shù)據(jù),那么插入前這個事務(wù)是看不到那條數(shù)據(jù)的,但是插入之后,這個事務(wù)就可以select到那個數(shù)據(jù)。也就是說,同樣的select語句,前后兩次數(shù)據(jù)的會多或者少(幻讀) - repeatable-read(RR)
在這個級別下,為了解決幻讀,添加gap lock。即在select到的數(shù)據(jù)中間添加gap鎖,其他想插入的事務(wù)必須要拿到這個gap鎖才能插入,否則必須等待這個事務(wù)釋放gap鎖。
4.SERIALIZABLE
在RR的基礎(chǔ)上,轉(zhuǎn)換所有 SELECT 語句為SELECT ... LOCK IN SHARE MODE。即所有的讀和寫都要加鎖。
- read-uncommited
-
死鎖
造成死鎖的原因都是因為加鎖的順序不同,具體原因可以分為業(yè)務(wù)和索引。-
業(yè)務(wù)導(dǎo)致的死鎖
業(yè)務(wù)層加鎖時順序不同
這種情況比較常見,也比較容易解決,在代碼中,兩個線程在加鎖的時候,加鎖順序不同,導(dǎo)致他們互相需要對方的鎖。
-
索引導(dǎo)致的死鎖
索引順序不同導(dǎo)致死鎖
這種情況就比較特殊,我們在業(yè)務(wù)層幾乎感覺不到這種死鎖,因為這個涉及到mysql對索引的操作。兩個語句同時需要加鎖,而且條件是在兩個不同的索引。這樣加鎖順序就取決于主鍵在兩個索引的順序了。如圖中所示,兩個索引的順序相反,加鎖的順序也就相反,容易死鎖。
我們需要注意的就是,如果操作一個有多個索引的數(shù)據(jù)表,盡量不要在多個索引上同時操作。
-
-
分析方法
- show engine innodb status
總結(jié)
- InnoDB是通過索引實現(xiàn)的行鎖
所以如果select中條件是一個沒有索引的列,那么會導(dǎo)致表鎖,如果事務(wù)隔離級別是RR,那么事務(wù)結(jié)束之前都不會釋放。 - 使用Gap鎖
如果select一個不存在的數(shù)據(jù),那么也會在對應(yīng)的區(qū)間添加gap鎖。
等于號的使用會影響范圍,不好的話會鎖住gap(next key) - 不使用索引
- 如果MySQL估計使用索引比全表掃描更慢,則不使用索引。例如,如果列key均勻分布在1和100之間,下面的查詢使用索引就不是很好:select * from table_name where key>1 and key<90;
- 如果使用MEMORY/HEAP表,并且where條件中不使用“=”進(jìn)行索引列,那么不會用到索引,head表只有在“=”的條件下才會使用索引
- 用or分隔開的條件,如果or前的條件中的列有索引,而后面的列沒有索引,那么涉及到的索引都不會被用到,例如:select * from table_name where key1='a' or key2='b';如果在key1上有索引而在key2上沒有索引,則該查詢也不會走索引
- 復(fù)合索引,如果索引列不是復(fù)合索引的第一部分,則不使用索引(即不符合最左前綴),例如,復(fù)合索引為(key1,key2),則查詢select * from table_name where key2='b';將不會使用索引
- 如果like是以‘%’開始的,則該列上的索引不會被使用。例如select * from table_name where key1 like '%a';該查詢即使key1上存在索引,也不會被使用
- 如果列為字符串,則where條件中必須將字符常量值加引號,否則即使該列上存在索引,也不會被使用。例如,select * from table_name where key1=1;如果key1列保存的是字符串,即使key1上有索引,也不會被使用。
- 重復(fù)加鎖
對不存在的記錄加排他鎖,都會加鎖成功。即gap鎖的x鎖不互斥。

