海量數據處理之數據庫索引

前言:本文第一部分討論數據庫的索引及其優化,主要以sql server爲例,第二部分我們從Mysql討論它背後的數據結構和算法原理。

第一部分,數據庫索引及其優化

一,什麼是索引

  數據庫索引好比是一本書前面的目錄,能加快數據庫的查詢速度。
  例如這樣一個查詢:select * from table1 where id=44。如果沒有索引,必須遍歷整個表,直到ID等於44的這一行被找到爲止;有了索引之後(必須是在ID這一列上建立的索引),直接在索引裏面找44(也就是在ID這一列找),就可以得知這一行的位置,也就是找到了這一行。可見,索引是用來定位的。
  索引分爲聚簇索引和非聚簇索引兩種,聚簇索引 是按照數據存放的物理位置爲順序的,而非聚簇索引就不一樣了;顯然在一個基本表上最多隻能建立一個聚簇索引。建立聚簇索引後,更新該索引列上的數據時,往往導致表中記錄的物理順序的變更,代價較大,因此對於經常更新得列不宜建立聚簇索引,聚簇索引能提高多行檢索的速度,而非聚簇索引對於單行的檢索很快。建立一個聚簇索引如:

create cluster index id on Student(id);

二,概述

  建立索引的目的是加快對錶中記錄的查找或排序。
  爲表設置索引要付出代價的:一是增加了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間(因爲索引也要隨之變動)。

爲什麼要創建索引

創建索引可以大大提高系統的性能。

第一,通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
第二,可以大大加快數據的檢索速度,這也是創建索引的最主要的原因。
第三,可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間。
第五,通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。

也許會有人要問:增加索引有如此多的優點,爲什麼不對錶中的每一個列創建一個索引呢?因爲,增加索引也有許多不利的方面。

第一,創建索引和維護索引要耗費時間,這種時間隨着數據量的增加而增加。
第二,索引需要佔物理空間,除了數據表佔數據空間之外,每一個索引還要佔一定的物理空間,如果要建立聚簇索引,那麼需要的空間就會更大。
第三,當對錶中的數據進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了數據的維護速度。

在哪建索引

索引是建立在數據庫表中的某些列的上面。在創建索引的時候,應該考慮在哪些列上可以創建索引,在哪些列上不能創建索引。一般來說,應該在這些列上創建索引:

在經常需要搜索的列上,可以加快搜索的速度;
在作爲主鍵的列上,強制該列的唯一性和組織表中數據的排列結構;
在經常用在連接的列上,這些列主要是一些外鍵,可以加快連接的速度;在經常需要根據範圍進行搜索的列上創建索引,因爲索引已經排序,其指定的範圍是連續的;
在經常需要排序的列上創建索引,因爲索引已經排序,這樣查詢可以利用索引的排序,加快排序查詢時間;
在經常使用在WHERE子句中的列上面創建索引,加快條件的判斷速度。

同樣,對於有些列不應該創建索引。一般來說,不應該創建索引的的這些列具有下列特點:

第一,對於那些在查詢中很少使用或者參考的列不應該創建索引。這是因爲,既然這些列很少使用到,因此有索引或者無索引,並不能提高查詢速度。相反,由於增加了索引,反而降低了系統的維護速度和增大了空間需求。

第二,對於那些只有很少數據值的列也不應該增加索引。這是因爲,由於這些列的取值很少,例如人事表的性別列,在查詢的結果中,結果集的數據行佔了表中數據行的很大比例,即需要在表中搜索的數據行的比例很大。增加索引,並不能明顯加快檢索速度。

第三,對於那些定義爲text, image和bit數據類型的列不應該增加索引。這是因爲,這些列的數據量要麼相當大,要麼取值很少,不利於使用索引。

第四,當修改性能遠遠大於檢索性能時,不應該創建索引。這是因爲,修改性能和檢索性能是互相矛盾的。當增加索引時,會提高檢索性能,但是會降低修改性能。當減少索引時,會提高修改性能,降低檢索性能。因此,當修改操作遠遠多於檢索操作時,不應該創建索引。

  精簡來說,索引是一種結構.在SQL Server中,索引和表(這裏指的是加了聚集索引的表)的存儲結構是一樣的,都是B樹,B樹是一種用於查找的平衡多叉樹.理解B樹的概念如下圖:

理解爲什麼使用B樹作爲索引和表(有聚集索引)的結構,首先需要理解SQL Server存儲數據的原理.

在SQL SERVER中,存儲的單位最小是頁(PAGE),頁是不可再分的。就像細胞是生物學中不可再分的,或是原子是化學中不可再分的最小單位一樣.這意味着,SQL SERVER對於頁的讀取,要麼整個讀取,要麼完全不讀取,沒有折中.

在數據庫檢索來說,對於磁盤IO掃描是最消耗時間的.因爲磁盤掃描涉及很多物理特性,這些是相當消耗時間的。所以B樹設計的初衷是爲了減少對於磁盤的掃描次數。如果一個表或索引沒有使用B樹(對於沒有聚集索引的表是使用堆heap存儲),那麼查找一個數據,需要在整個表包含的數據庫頁中全盤掃描。這無疑會大大加重IO負擔.而在SQL SERVER中使用B樹進行存儲,則僅僅需要將B樹的根節點存入內存,經過幾次查找後就可以找到存放所需數據的被葉子節點包含的頁!進而避免的全盤掃描從而提高了性能.

下面,通過一個例子來證明:

在SQL SERVER中,表上如果沒有建立聚集索引,則是按照堆(HEAP)存放的,假設我有這樣一張表:

1

現在這張表上沒有任何索引,也就是以堆存放,我通過在其上加上聚集索引(以B樹存放)來展現對IO的減少:

2

三、理解聚集索引和非聚集索引


    在SQL SERVER中,最主要的兩類索引是聚集索引和非聚集索引。可以看到,這兩個分類是圍繞聚集這個關鍵字進行的.那麼首先要理解什麼是聚集.

    聚集在索引中的定義:

    爲了提高某個屬性(或屬性組)的查詢速度,把這個或這些屬性(稱爲聚集碼)上具有相同值的元組集中存放在連續的物理塊稱爲聚集。

    簡單來說,聚集索引就是:

    3

    在SQL SERVER中,聚集的作用就是將某一列(或是多列)的物理順序改變爲和邏輯順序相一致,比如,我從adventureworks數據庫的employee中抽取5條數據:

    4

    當我在ContactID上建立聚集索引時,再次查詢:

    5

    在SQL SERVER中,聚集索引的存儲是以B樹存儲,B樹的葉子直接存儲聚集索引的數據:

    grid.ai

    因爲聚集索引改變的是其所在表的物理存儲順序,所以每個表只能有一個聚集索引.

非聚集索引

     因爲每個表只能有一個聚集索引,如果我們對一個表的查詢不僅僅限於在聚集索引上的字段。我們又對聚集索引列之外還有索引的要求,那麼就需要非聚集索引了.

     非聚集索引,本質上來說也是聚集索引的一種.非聚集索引並不改變其所在表的物理結構,而是額外生成一個聚集索引的B樹結構,但葉子節點是對於其所在表的引用,這個引用分爲兩種,如果其所在表上沒有聚集索引,則引用行號。如果其所在表上已經有了聚集索引,則引用聚集索引的頁.

     一個簡單的非聚集索引概念如下:

     6

     可以看到,非聚集索引需要額外的空間進行存儲,按照被索引列進行聚集索引,並在B樹的葉子節點包含指向非聚集索引所在表的指針.

     MSDN中,對於非聚集索引描述圖是:

     grid.ai

     可以看到,非聚集索引也是一個B樹結構,與聚集索引不同的是,B樹的葉子節點存的是指向堆或聚集索引的指針.

     通過非聚集索引的原理可以看出,如果其所在表的物理結構改變後,比如加上或是刪除聚集索引,那麼所有非聚集索引都需要被重建,這個對於性能的損耗是相當大的。所以最好要先建立聚集索引,再建立對應的非聚集索引.

聚集索引 VS 非聚集索引


      前面通過對於聚集索引和非聚集索引的原理解釋.我們不難發現,大多數情況下,聚集索引的速度比非聚集索引要略快一些.因爲聚集索引的B樹葉子節點直接存儲數據,而聚集索引還需要額外通過葉子節點的指針找到數據.

      還有,對於大量連續數據查找,非聚集索引十分乏力,因爲非聚集索引需要在非聚集索引的B樹中找到每一行的指針,再去其所在表上找數據,性能因此會大打折扣.有時甚至不如不加非聚集索引.

      因此,大多數情況下聚集索引都要快於非聚集索引。但聚集索引只能有一個,因此選對聚集索引所施加的列對於查詢性能提升至關緊要.

數據庫優化

此外,除了數據庫索引之外,在LAMP結果如此流行的今天,數據庫(尤其是MySQL)性能優化也是海量數據處理的一個熱點。下面就結合自己的經驗,聊一聊MySQL數據庫優化的幾個方面。

首先,在數據庫設計的時候,要能夠充分的利用索引帶來的性能提升,至於如何建立索引,建立什麼樣的索引,在哪些字段上建立索引,上面已經講的很清楚了,這裏不在贅述。另外就是設計數據庫的原則就是儘可能少的進行數據庫寫操作(插入,更新,刪除等),查詢越簡單越好。

其次,配置緩存是必不可少的,配置緩存可以有效的降低數據庫查詢讀取次數,從而緩解數據庫服務器壓力,達到優化的目的,一定程度上來講,這算是一個“圍魏救趙”的辦法。可配置的緩存包括索引緩存(key_buffer),排序緩存(sort_buffer),查詢緩存(query_buffer),表描述符緩存(table_cache)。

第三,切表,切表也是一種比較流行的數據庫優化方法。分表包括兩種方式:橫向分表和縱向分表,其中,橫向分表比較有使用意義,故名思議,橫向切表就是指把記錄分到不同的表中,而每條記錄仍舊是完整的(縱向切表後每條記錄是不完整的),例如原始表中有100條記錄,我要切成2個表,那麼最簡單也是最常用的方法就是ID取摸切表法,本例中,就把ID爲1,3,5,7。。。的記錄存在一個表中,ID爲2,4,6,8,。。。的記錄存在另一張表中。雖然橫向切表可以減少查詢強度,但是它也破壞了原始表的完整性,如果該表的統計操作比較多,那麼就不適合橫向切表。橫向切表有個非常典型的用法,就是用戶數據:每個用戶的用戶數據一般都比較龐大,但是每個用戶數據之間的關係不大,因此這裏很適合橫向切表。最後,要記住一句話就是:分表會造成查詢的負擔,因此在數據庫設計之初,要想好是否真的適合切表的優化:

第四,日誌分析,在數據庫運行了較長一段時間以後,會積累大量的LOG日誌,其實這裏面的蘊涵的有用的信息量還是很大的。通過分析日誌,可以找到系統性能的瓶頸,從而進一步尋找優化方案。

以上講的都是單機MySQL的性能優化的一些經驗,但是隨着信息大爆炸,單機的數據庫服務器已經不能滿足我們的需求,於是,多多節點,分佈式數據庫網絡出現了,其一般的結構如下:

分佈式數據庫結構

 

 第二部分 MySQL索引背後的數據結構及算法

這一部分可以參考本博客前面轉的一篇MySQL索引背後的數據結構及算法原理(轉),爲了防止代碼重複這裏就給出函數調用即可.

發佈了36 篇原創文章 · 獲贊 17 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章