目錄
一、MySQL存儲引擎
注意,MySQL的存儲引擎是表級的,同一個數據庫的不同表可以使用不同的引擎
1.1 Innodb引擎
Innodb引擎現在是MySQL的默認引擎。Innodb引擎提供了對數據庫ACID事務的支持,並且實現了SQL標準的四種隔離級別。該引擎還提供了行級鎖和外鍵約束,它的設計目標是處理大容量數據庫系統,它本身其實就是基於MySQL後臺的完整數據庫系統,MySQL運行時Innodb會在內存中建立緩衝池,用於緩衝數據和索引。但是該引擎不支持FULLTEXT類型的索引,而且它沒有保存表的行數,當SELECT COUNT(*) FROM TABLE時需要掃描全表。當需要使用數據庫事務時,該引擎當然是首選。由於鎖的粒度更小,寫操作不會鎖定全表,所以在併發較高時,使用Innodb引擎會提升效率。但是使用行級鎖也不是絕對的,如果在執行一個SQL語句時MySQL不能確定要掃描的範圍,InnoDB表同樣會鎖全表。
1.2 MyISAM引擎
在MySQL5.1之前,MyISAM是MySQL默認的引擎,它沒有提供對數據庫事務的支持,也不支持行級鎖和外鍵,因此當INSERT(插入)或UPDATE(更新)數據時即寫操作需要鎖定整個表,效率便會低一些。不過和Innodb不同,MyISAM中存儲了表的行數,於是SELECT COUNT(*) FROM TABLE時只需要直接讀取已經保存好的值而不需要進行全表掃描。如果表的讀操作遠遠多於寫操作且不需要數據庫事務的支持,那麼MyIASM也是很好的選擇。
1.3 InNoDB與MyISAM異同
- InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text (InNoDB從1.2.X版本開始支持全文搜索的技術)等索引,不支持 Hash 索引,但是給了又有一個特殊的解釋:InnoDB存儲引擎 是支持hash索引的,不過,我們必須啓用,hash索引的創建由InnoDB存儲引擎引擎自動優化創建,是數據庫自身創建並使用,DBA(數據庫管理員)無法干預;
- MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
- Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
- MyISAM引擎不支持外鍵,InnoDB支持外鍵
- MyISAM引擎的表在大量高併發的讀寫下會經常出現表損壞的情況
- 對於count()查詢來說MyISAM更有優勢,MyISAM直接通過計數器獲取,MyISAM會有一個空間轉專門又來存儲行數。InnoDB需要通過掃描全部數據,雖然InNoDB存儲引擎是支持行級別鎖,InNoDB是行級別鎖,是where對他主鍵是有效,非主鍵的都會鎖全表的
- MyISAM引擎的表的查詢、更新、插入的效率要比InnoDB高,如果你的數據量是百萬級別的,並且沒有任何的事務處理,那麼用MyISAM是性能最好的選擇。並且MyISAM可以節省很多內存,因爲MyISAM索引文件是與數據文件分開放置,並且索引是有壓縮,內存使用率提高不少
- 平臺承載的大部分項目是讀多寫少的項目,MyISAM讀性能比InNoDB強很多
MyISAM和innoDB的區別總結如下:
MyISAM和innoDB引擎對比 |
MyISAM |
innoDB |
索引類型 |
非聚簇 |
聚簇 |
支持事務 |
是 |
否 |
支持表鎖 |
是 |
是 |
支持行鎖 |
否 |
是(默認) |
支持外鍵 |
否 |
是 |
支持全文索引 |
是 |
是(5.6以後支持) |
適用操作類型 |
大量select下使用 |
大量insert、delete和update下使用 |
1.4 兩種引擎的選擇
- 大尺寸的數據集趨向於選擇InnoDB引擎,因爲它支持事務處理和故障恢復。數據庫的大小決定了故障恢復的時間長短,InnoDB可以利用事務日誌進行數據恢復,這會比較快。
- 主鍵查詢在InnoDB引擎下也會相當快,不過需要注意的是如果主鍵太長也會導致性能問題,因爲在檢索索引樹的時候不管是主鍵索引還是輔助鍵索引最終都是會通過比較主鍵來進行檢索進而取得行數據的,如果逐漸太長,那麼比較主鍵的操作也會變複雜。
- 大批的INSERT語句(在每個INSERT語句中寫入多行,批量插入)在MyISAM下會快一些
- 但是UPDATE語句在InnoDB下則會更快一些,尤其是在併發量大的時候。
二、索引(Index)
索引(Index)是幫助MySQL高效獲取數據的排好序的數據結構。MyISAM和Innodb都使用了B+樹這種數據結構做爲索引。每建一個索引就會將索引數據按照B+樹的數據結構創建,將相關數據冗餘的存儲一份,但是這樣能加快搜索速度,很明顯的一個用空間換時間的例子。
數據庫索引好比是一本書前面的目錄,能加快數據庫的查詢速度。索引分爲聚簇索引和非聚簇索引兩種,在一個表中只能有一個聚集索引,在InnoDB引擎中以主鍵作爲聚集索引,而非聚集索引可以有多個,除了聚集索引其他都是非聚集索引。
可以說數據庫必須有索引,沒有索引則檢索過程變成了順序查找,O(n)的時間複雜度幾乎是不能忍受的。我們非常容易想象出一個只有單關鍵字組成的表如何使用B+樹進行索引,只要將關鍵字存儲到樹的節點即可。當數據庫一條記錄裏包含多個字段時,一棵B+樹就只能存儲主鍵(指結點中存儲存儲主鍵,使用主鍵來進行檢索),如果檢索的是非主鍵字段,則主鍵索引失去作用,又變成順序查找了。這時應該在第二個要檢索的列上建立第二套索引(使這一課B+樹的結點中存儲輔助鍵,使用輔助鍵來進行檢索),這個就是二級索引或者叫輔助鍵索引,除了主鍵索引之外其他的所有索引都是二級索引。 這個索引由獨立的B+樹來組織。有兩種常見的方法可以解決多個B+樹訪問同一套表數據的問題,一種叫做聚簇索引(clustered index ),一種叫做非聚簇索引(secondary index)。這兩個名字雖然都叫做索引,但這並不是一種單獨的索引類型,而是一種數據存儲方式。
不同的存儲引擎對聚集索引和非聚集索引的實現方式是不同的:
- 對於InnoDB來說,使用聚集索引的主鍵索引行數據和主鍵B+樹存儲在一起,使用非聚集索引的輔助鍵B+樹只存儲輔助鍵和主鍵,主鍵和非主鍵B+樹幾乎是兩種類型的樹。輔助鍵索引也稱爲非主鍵索引或二級索引或次級索引
- 對於MyISAM來說,它不支持聚集索引,所以主鍵索引和輔助鍵索引都是非聚集索引。主鍵B+樹和輔助鍵B+樹在葉子節點存儲指向真正數據行的指針,通過主鍵索引樹或者輔助鍵索引樹都可以直接找到相應數據行的全部數據。
通過上面的對聚集索引和非聚集索引的簡單介紹我們就可以發現聚集索引的主鍵索引和輔助鍵索引葉子結點樹存儲內容是有區別的。而非聚集索引的主鍵索引和輔助鍵索引的存儲結構其實是沒有區別,葉子節點中數據區存儲的都是指向數據行數據的指針,唯一的區別就是索引樹節點中的索引區存儲的索引字段不同了。
2.1 InnoDB存儲引擎索引的實現
InnoDB支持是聚簇索引,InnoDB存儲引擎將主鍵索引用聚集索引來管理,二級索引用非聚集索引來管理。將主鍵組織到一棵B+樹中,而行數據就儲存在葉子節點上,若使用"where id = 14"這樣的條件查找主鍵(使用主鍵索引),則按照B+樹的檢索算法即可查找到對應的葉節點,之後獲得行數據。若對Name列進行條件搜索(使用二級索引),則需要兩個步驟:第一步在輔助索引B+樹中檢索Name,到達其葉子節點獲取對應的主鍵。第二步使用主鍵在主索引B+樹種再執行一次B+樹檢索操作(回表),最終到達葉子節點即可獲取整行數據。
以下爲InnoDB存儲引擎的表文件:
- .frm:表的定義,就是描述表結構的文件
- .ibd:表的數據文件和索引文件 i表示的是InnoDB和Index d表示的是Data
由表文件結構就可以看出,InnoDB是支持聚集索引的,它的表數據文件和索引文件放在一起。
- 爲什麼InnoDB非聚集索引的輔助鍵索引結構葉子節點存儲的是主鍵值?(一致性和節省存儲空間)
因爲如果InnoDB的輔助鍵也將行數據全部存到葉子節點,雖然能避免使用輔助鍵索引進行檢索的時候需要檢索兩次才找到行數據,但是這樣會造成冗餘的存儲數據(因爲同一份數據在物理存儲器中只能存放在同一個位置,如果像上面這樣在非聚集索引中也將表數據存在葉子節點,就只能在存儲器中冗餘的存兩份相同的表數據),同一份數據在硬盤中存儲兩次,就會造成數據浪費,還有一點就是同一份數據存在兩個索引上,一旦對數據庫的數據進行修改,還需要保證兩顆索引樹的數據一致性,這又是一個很麻煩的事情,所以輔助鍵索引樹的葉子節點存儲主鍵值,可以節約空間和保證數據一致性,修改行數據只需要修改主鍵索引中的行數據就可以了,不需要再對輔助索引進行修改。
- 爲什麼InnoDB表必須有主鍵,並且推薦使用整型的自增主鍵?不用UUID
由上面的輔助鍵索引的檢索過程可以看出,輔助鍵索引必須依賴於主鍵索引才能查找到完整的行數據,表數據就存在主鍵索引樹中,所以InnoDB引擎必須要有主鍵索引,如果自己不設置主鍵索引InnoDB也會自己選一列作爲索引,如果自己建的表中沒有合適的列作爲索引,InnoDB也會自動創建一個隱藏列來作爲主鍵。使用整型自增的主鍵是爲了方便檢索,很顯然整型數據的比較效率要高於隨機ASCII碼字符串的比較。
如果InnoDB引擎的表自己不主動定義主鍵,那麼MySQL會自己想辦法創建主鍵,過程如下:
- 如果沒有爲表定義PRIMARY KEY,MySQL將找到第一個UNIQUE索引,其中所有鍵列都是NOT NULL,而InnoDB將它用作聚集索引。
- 如果表沒有PRIMARY KEY或合適的UNIQUE索引,InnoDB會在包含行ID值的合成列內部生成名爲GEN_CLUST_INDEX的隱藏聚集索引 。這些行按InnoDB分配給此類表中的行的ID排序。行ID是一個6字節的字段,在插入新行時會單調增加。因此,由行ID排序的行在物理上處於插入順序。
- 之前一直有一個誤區,認爲主鍵索引就是聚集索引
糾正一下這個誤區,因爲MySQL的默認存儲引擎是InnoDB,所以創建的主鍵索引就是聚集索引。其實主鍵索引和聚集索引並沒有必然聯繫,非聚集索引也有主鍵索引。聚集索引只是一種數據存儲的方式,不同的存儲引擎有不同的實現。只是因爲MySQL默認是InnoDB引擎,所以創建的主鍵也就默認是聚集索引。即主鍵是聚集索引還是非聚集索引取決於這個表的存儲引擎是InnoDB還是MyISAM。
2.2 MyISAM索引實現(非聚集索引)
MyISAM使用的是非聚簇索引,不支持聚集索引。非聚簇索引的兩棵B+樹看上去沒什麼不同,節點的結構完全一致只是存儲的內容不同而已,主鍵索引B+樹的節點存儲了主鍵,輔助鍵索引B+樹存儲了輔助鍵。表數據存儲在獨立的地方,這兩顆B+樹的葉子節點都使用一個地址指向真正的表數據(指向的就是對應的這一行全部的字段數據),對於表數據來說,主鍵索引樹和輔助鍵索引樹沒有任何差別。由於索引樹是獨立的,通過輔助鍵檢索無需訪問主鍵的索引樹。
以下爲MyISAM存儲引擎下的表文件:
- .frm:表的定義,就是描述表結構的文件
- .MYD:數據存儲文件 D表示的是Data MY表示的是MyISAM
- .MYI:索引存儲文件 I表示的是Index
由表文件組成可以看出,MyISAM存儲引擎是使用的非聚集索引,它的數據文件和索引文件是分開的。
2.3 InnoDB的索引與InnoDB的索引的區別
爲了更形象說明這兩種存儲引擎中的索引的區別,我們假想一個表如下圖存儲了4行數據。其中Id作爲主索引,Name作爲輔助索引。圖示清晰的顯示了聚簇索引和非聚簇索引的差異。
我們重點關注聚簇索引,看上去InnoDB的效率明顯要低於MyISAM,因爲每次使用輔助索引檢索都要經過兩次B+樹查找,而MyISAM的非聚集索引使用輔助鍵查詢只需要一次就能找到一整行的元組數據。這不是多此一舉嗎?聚簇索引的優勢在哪?
1 由於行數據和葉子節點存儲在一起,這樣主鍵和行數據是一起被載入內存的,找到葉子節點就可以立刻將行數據返回了,而不用再通過存儲的地址再去硬盤中查詢一次數據行,如果按照主鍵Id來組織數據,獲得數據更快。
2 輔助索引使用主鍵作爲"指針" 而不是使用地址值作爲指針的好處是,減少了當出現行移動或者數據頁分裂時輔助索引的維護工作,使用主鍵值當作指針會讓輔助索引佔用更多的空間,換來的好處是InnoDB在移動行時無須更新輔助索引中的這個"指針"。也就是說行的位置(實現中通過16K的Page來定位,詳細可以看計算機操作系統分頁管理相關章節)會隨着數據庫裏數據的修改而發生變化(B+樹節點分裂以及Page的分裂),使用InnoDB就可以保證不管這個主鍵B+樹(聚集索引)的節點如何變化,輔助索引樹(非聚集索引)都不受影響。
3 聚集索引的數據都是按順序存放的,所以如果查詢條件是主鍵,使用主鍵索引,那麼聚集索引會非常快,因爲相同範圍段的數據都是連續存放在一起的。即聚集索引表記錄的物理排列順序與索引的邏輯排列順序一致,優點是查詢速度快,一旦符合條件的第一個索引值的紀錄被找到,具有連續索引值的記錄也一定物理的緊跟其後。聚集索引的主鍵索引的葉子節點中直接存儲行數據,又因爲B+樹的葉子節點之間都會用過指針相連,所以直接就能很快將這個範圍內的數據全部獲取。但是非聚集索引的主鍵索引雖然在邏輯上相同範圍的葉子節點是順序存儲在一起的,但是真實的行數據是在硬盤中散列存儲的,要想獲取數據還需要將存儲在葉子節點中的地址取出,根據地址再去硬盤中獲取數據,效率就慢了很多。這個是聚集索引的主鍵索引的優勢,也是第一條優勢的具體體現。根據局部性原理,這也會提高檢索效率。
局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。
聚集索引的劣勢有哪些?
1 聚集索引的缺點是對錶進行修改速度較慢,這是爲了保持表中的記錄的物理順序與索引的順序一致,而把記錄插入到數據頁的相應位置,必須在數據頁中進行數據重排,降低了執行速度。插入數據時速度要慢(時間花費在“物理存儲的排序”上,也就是首先要找到位置然後插入)。而非聚集索引指定了表中記錄的邏輯順序,但記錄的物理順序和索引的順序不一致,聚集索引和非聚集索引都採用了B+樹的結構,但非聚集索引的葉子層並不與實際的數據頁相重疊,而採用葉子層包含一個指向表中的記錄在數據頁中的指針的方式(這個指針可能是真實的物理地址,也可能是對應的主鍵值,這根據不同的存儲引擎對它實現是不同的)。非聚集索引比聚集索引層次多,添加記錄不會引起數據順序的重組。
總的來說,聚集索引查詢數據速度快,插入數據速度慢;非聚集索引反之。他們各自優缺點就是相反的。所以非聚集索引的優缺點看上面聚集索引的優缺點就夠了。
2.4 下面用一組實例來比較聚集索引和非聚集索引的根本區別
2.4.1 根本區別
聚集索引和非聚集索引的根本區別是表記錄的排列順序和與索引的排列順序是否一致。
2.4.2 聚集索引
聚集索引表記錄的排列順序和索引的排列順序一致(以InnoDB聚集索引的主鍵索引來說,葉子節點中存儲的就是行數據,行數據在物理儲器中的真實地址就是按照主鍵索引樹形成的順序進行排列的),所以查詢效率快,只要找到第一個索引值記錄,其餘就連續性的記錄在物理也一樣連續存放。聚集索引對應的缺點就是修改慢,因爲爲了保證表中記錄的物理和索引順序一致,在記錄插入的時候,會對數據頁重新排序(因爲在真實物理存儲器的存儲順序只能有一種,而插入新數據必然會導致主鍵索引樹的變化,主鍵索引樹的順序發生了改變,葉子節點中存儲的行數據也要隨之進行改變,就會發生大量的數據移動操作,所以效率會慢)。因爲在物理內存中的順序只能有一種,所以聚集索引在一個表中只能有一個。
2.4.3 非聚集索引
非聚集索引制定了表中記錄的邏輯順序,但是記錄的物理和索引不一定一致(在邏輯上數據是按順序排存放的,但是物理上在真實的存儲器中是散列存放的),兩種索引都採用B+樹結構,非聚集索引的葉子層並不和實際數據頁相重疊,而採用葉子層包含一個指向表中的記錄在數據頁中的指針方式。非聚集索引層次多,不會造成數據重排。所以如果表的讀操作遠遠多於寫操作,那麼就可以使用非聚集索引。
2.4.4 例子對比兩種索引
聚集索引就類似新華字典中的拼音排序索引,都是按順序進行,例如找到字典中的“愛”,就裏面順序執行找到“癌”。而非聚集索引則類似於筆畫排序,索引順序和物理順序並不是按順序存放的。總的來說,聚集索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊
索引創建Demo
create database IndexDemo
go
use IndexDemo
go
create table ABC
(
A int not null,
B char(10),
C varchar(10)
)
go
insert into ABC select 1,'B','C'
union select 5,'B','C'
union select 7,'B','C'
union select 9,'B','C'
go select * from abc
這個時候查看錶記錄,如圖一顯示
這個時候插入一條數據,
insert into abc values('6','B','C')
此時的查詢記錄爲圖二展示
添加聚集索引,再查詢數據顯示爲圖三,此時發現表的順序發生了變化,此時的排序按A字段的遞增排序。這就說明了使用聚集索引如果插入新數據會進行重新排序
create clustered index CLU_ABC on abc(A)
刪除聚集索引,會發現表的順序不會發生改變。
create nonclustered index NONCLU_ABC on abc(A)
聚集索引和非聚集索引的區別總結:
- 聚集索引一個表只能有一個,而非聚集索引一個表可以存在多個
- 聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,物理存儲並不連續
- 聚集索引:物理存儲按照索引排序;聚集索引是一種索引組織形式,索引的鍵值邏輯順序決定了表數據行的物理存儲順序
- 非聚集索引:物理存儲不按照索引排序;非聚集索引則就是普通索引了,僅僅只是對數據列創建相應的索引,不影響整個表的物理存儲順序.
- 索引是通過B+樹的數據結構來描述的,我們可以這麼理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。
三、如何選擇聚集索引和非聚集索引
動作描述 |
使用聚集索引 |
使用非聚集索引 |
列經常被分組排序 |
是 |
是 |
返回某範圍內的數據 |
是 |
否 |
一個或極少不同值 |
否 |
否 |
小數目的不同值 |
是 |
否 |
大數目的不同值 |
否 |
是 |
頻繁更新的列 |
否 |
是 |
外鍵列 |
是 |
是 |
主鍵列 |
是 |
是 |
頻繁修改索引列 |
否 |
是 |
四、總結
我們需要搞清楚以下幾個問題:
第一:聚集索引的約束是唯一性,是否要求字段也是唯一的呢? 不要求唯一!
分析:如果認爲是的朋友,可能是受系統默認設置的影響,一般我們指定一個表的主鍵,如果這個表之前沒有聚集索引,同時建立主鍵時候沒有強制指定使用非聚集索引,SQL會默認在此字段上創建一個聚集索引,而主鍵都是唯一的,所以理所當然的認爲創建聚集索引的字段也需要唯一。
結論:聚集索引可以創建在任何一列你想創建的字段上,這是從理論上講,實際情況並不能隨便指定,否則在性能上會是惡夢。
第二:爲什麼聚集索引可以創建在任何一列上,如果此表沒有主鍵約束,即有可能存在重複行數據呢?
粗一看,這還真是和聚集索引的約束相背,但實際情況真可以創建聚集索引。
分析其原因是:如果未使用 UNIQUE 屬性創建聚集索引,數據庫引擎將向表自動添加一個四字節 uniqueifier 列。必要時,數據庫引擎 將向行自動添加一個 uniqueifier 值,使每個鍵唯一。此列和列值供內部使用,用戶不能查看或訪問。
第三:是不是聚集索引就一定要比非聚集索引性能優呢?
如果想查詢學分在60-90之間的學生的學分以及姓名,在學分上創建聚集索引是否是最優的呢?
答:否。既然只輸出兩列,我們可以在學分以及學生姓名上創建聯合非聚集索引,此時的索引就形成了覆蓋索引,即索引所存儲的內容就是最終輸出的數據,這種索引在比以學分爲聚集索引做查詢性能更好。就是說我們用學分去建立非聚集索引,那麼搜索出來之後結點中的索引數據區只存有學分的數據,還需要根據葉子節點中數據區中的地址去查詢,但是如果直接將要查詢的學分字段和姓名字段創建一個聯合索引(也是非聚集索引),這樣在索引樹中查找到數據之後直接就能在節點的索引數據區取得兩個索引值,就不用再通過葉子節點中數據區裏面的地址再去查詢一次了。
第四:在MySQL數據庫中通過什麼描述聚集索引與非聚集索引的?
索引是通過B+樹的形式進行描述的,我們可以這樣區分聚集與非聚集索引的區別:InnoDB中的聚集索引的葉節點就是最終的數據節點,InnoDB中的非聚集索引葉子節點指向的是相應的主鍵值。而MyISAM中非聚集索引的主鍵索引樹和二級索引樹的葉節仍然是索引節點,但它有一個指向最終數據的指針。
第五:在主鍵是創建聚集索引的表在數據插入上爲什麼比主鍵上創建非聚集索引錶速度要慢?
聚集索引的缺點是對錶進行修改速度較慢,這是爲了保持表中的記錄的物理順序與索引的順序一致,而把記錄插入到數據頁的相應位置,必須在數據頁中進行數據重排,降低了執行速度。插入數據時速度要慢(時間花費在“物理存儲的排序”上,也就是首先要找到位置然後插入)。非聚集索引指定了表中記錄的邏輯順序,但記錄的物理順序和索引的順序不一致,聚集索引和非聚集索引都採用了B+樹的結構,但非聚集索引的葉子層並不與實際的數據頁相重疊,而採用葉子層包含一個指向表中的記錄在數據頁中的指針的方式。非聚集索引比聚集索引層次多,添加記錄不會引起數據順序的重組。這就是爲什麼主鍵上創建非聚集索引比主鍵上創建聚集索引在插入數據時要快的真正原因。
其他相關文章:【MySQL】InnoDB存儲引擎,MyISAM存儲引擎,聚集索引,非聚集索引,主鍵索引,二級索引他們之間的關係梳理
【MySQL】InnoDB行格式、數據頁結構以及索引底層原理分析
【MySQL】主從複製實現原理詳解
【MySQL】MySQL分庫分表詳解
【MySQL】MySQL的鎖與事務隔離級別詳解
參考資料:高性能MySQL(第3版)