如何成為建資料庫索引的高手?資料庫高階

2021-07-09 22:35:12 字數 3234 閱讀 8931

今天來聊聊資料庫裡的索引,你知道的,網上這樣的文章一抓一大把的, 基本都是從索引的原理說起,講到索引的分類, 物理組織和儲存形式,如何找到對應的記錄,如何構建複雜的索引等等 ,如果我再寫一篇這樣的就沒意思了,而且這些未必真的是大家(尤其是開發同學)關心的。所以我今天打算以乙個不同的角度來講下索引,而且針對b+tree索引,希望大家看了會有所幫助。

對於乙個sql,開發同學最關心的啥? 我覺得並不是這個sql在資料庫裡面是如何執行的,而是這條sql是否能盡快的返回結果,前面我們在講連線池的時候提到過,在sql的生命週期裡,每乙個環節都有足夠的優化空間,但是我們有沒有想過,sql優化的本質是啥?終極目標又是啥?其實優化本質上就是減少sql對資源的消耗和依賴,正如資料庫優化的終極大招是do nothing in database一樣,sql優化的終極目的也是consume no resource。

資源有兩個特性:首先資源是有限的,大家都搶著用就會有瓶頸的,所以sql的瓶頸可能是由資源緊張產生的。其次資源是有代價的,並且代價各異,比如記憶體的時延100ns, ssd100us,sas盤10ms,網路更高,那麼訪問cpu l1/l2/l3 cache的代價就比訪問記憶體的要低,訪問記憶體資源的代價要比訪問硬碟資源的代價低,所以sql的瓶頸也可能是訪問了代價比較高的資源導致的。現代計算機體系下,機器上粗粒度的資源就那麼幾種,無非就是cpu,記憶體,硬碟,和網路。那麼我們來看下sql需要消耗哪些資源:比較、排序、sql解析、函式或邏輯運算需要用到cpu;快取資料訪問,臨時資料存放需要用到記憶體;冷資料讀取,大資料量的排序和關聯,資料寫入落盤,需要訪問硬碟;sql請求互動,結果集返回需要網路資源。那麼我們在資料庫裡面對sql的優化思路,自然是減少sql的解析,減少複雜的運算,減少資料處理的規模,減少對物理io的依賴,減少伺服器和客戶端的網路互動, 那麼如果解釋清楚了索引如何能夠幫助做到這幾點,這篇文章的目的就達到了。

不過先不忙著解釋這些,先讓大家成為建索引的高手再說,哈哈,你沒看錯,成為索引高手就這麼簡單,三招速成,再多我也不會了,練完三招後上面這個問題也自然解釋清楚了,好, 讓我們拿下面的查詢sql來開始練招吧。

select cno, fname

from cust

where lname = :lname and city = :city

order by fname

第一招就是構建一星索引,根據where後面等值的條件,或者範圍的條件來構建索引,即index(lname,city) 。教科書上一般都說索引是為了能以最快的速度定位到想要的資料,即用空間來換時間,這當然沒錯,但是你有沒有想過,快速定位了你想要的資料後,也就過濾掉了不必要的資料,所以一星索引的核心就是利用索引來盡可能的過濾不必要的資料,減少資料處理的規模,對於rdbms來說是極為關鍵的,比如說cust表有1000000行,city的過濾度是10%,lname的過濾度是0.1%,那麼如果沒有索引,你不得不把表裡所有的一百萬行資料都讀出來,做處理,但是如果有了這個一星索引,需要處理的資料被極大的縮小了,只需要根據索引找到符合條件的索引葉子節點的範圍,讀取0.1%*10%*1000000=100rows就可以了,哪怕我們樂觀的假定產生的都是邏輯io, 而不是物理io,單次的差別就已經很明顯了,更別說是執行頻率很高的時候了,我們線上很多爛sql對db造成了影響,一看機器邏輯讀都好幾百萬了,基本上就可以定位是sql索引缺失,或者不合理造成的。當理解了這個時候,你就一定不會產生乙個誤區,在硬體越好越好,時延越來越低的今天,是不是索引還有存在的必要。

第二招就是構建二星索引, 針對上面的case, 我們構建索引如下index(lname,city,fname),基本的想法就是利用索引的有序性,把消除ordby或者group by等需要排序的操作,因為大家都知道排序是非常消耗cpu資源的,大量的排序操作會把user cpu搞得很高,即使cpu吃得消,如果資料量比較大,需要排序的資料放不下記憶體的sort buffer,只能悲劇的和外存換進換出,效能下降的就不是一點兩點了,這時候利用索引避免排序的優勢就明顯的體現出來了。

想必第三招你沒學就已經會了,沒錯,第三招就是構建三星索引,即index(lname,city,fname,cno), 跟之前的二星索引的差別在於, 在索引中額外新增了要查詢的列cno,這就是所謂的索引覆蓋,即在索引的葉子節點就能夠讀到查詢sql所需要的所有資訊,而不需要回原表去查詢了,在目前記憶體如此充足的情況下,很多時候,除了root節點和branch結構,甚至整個索引都是可以被放入記憶體的,這樣能大概率的避免,至少是減少物理io。

也許你會說,這招式都是最理想的狀態,現實的sql千變萬化,有各種奇葩的條件,有很多動態的sql,有多表關聯的sql,肯定不能拿上面說的三腳貓的招數硬往上套, 沒錯,實際情況下確實要考慮這樣那樣的因素,我們也沒辦法構建所有的索引都是三星的,我們只能根據實際情況, 構建最佳的索引,而非理想的索引,但是萬變不離其宗,理解了這三招的原理,就能夠見招拆招了,無招勝有招了。比如各種奇葩的條件,那我們選擇那些過濾性最好的, 比如動態的sql,我們就抓住主幹的那些sql,比如兩表關聯(mysql), 因為那就nest loop一種,那就用小表驅動大表,在關聯字段各自盡可能的構建最優索引。 

我們前面也提到了,索引其實是一種權衡,是一種拿空間來換時間的藝術,所以極左或者極右都是不恰當的,建立過多的索引所帶來的空間損耗 ,和對dml所產生的負擔,在某些極端場景下,都不能被忽視, 對於dml效能損耗的優化,除了只建立必要的索引外,有些nosql實現了二級索引,但是索引是採用非同步方式維護,不在乙個事務裡,這是通過犧牲強一致性來提高效能, 但是rdbms還做不到,另外在innodb上,我們會推薦使用業務無關的自增字段來作為主鍵,提高順序插入效能的同時,還能避免過多的索引**。對於空間成本上的優化,同樣可以有些技巧,還是拿innodb舉例,我們推薦使用數字型主鍵,而不推薦使用大字段作為主鍵的重要原因在於,大字段主鍵會極大的增大二級索引所占用的空間,因為二級索引葉子節點包含指向的主鍵,另外在oracle上,我們會定期rebuild index來節省索引所占用的空間。

同時b+tree索引,作為一種面向磁碟&ssd的資料結構,相對來說,查詢和寫入效能也是相對比較平衡的,讀寫的時間複雜度都在o(log2n),寫入上因為採用的是update-in-place的方式 ,每次寫入的時候需要先通過隨機查詢來找到要寫入的位置,效能會不是那麼好,當然你也可以選擇類似lsm_tree這樣的實現(包括ob自己實現的btree),通過犧牲一定程度的讀效能,來提高寫的效能。未來會不會出現一種能更完美的資料結構,能夠同時更高效的支援讀取和寫入,是一件比較值得期待的事情。

說了這麼多, 總結一下,我認為那麼在不考慮業務層面優化的前提假設下,索引是最有效的藥方,其他的優化方式與之相比都只能是看成偏方了,而且b-tree作為普遍採用的資料結構,基本上是通用於多種關係型資料庫的,記得我從oracle轉mysql的時候,索引的運用基本上能平滑過渡,所以希望大家都能了解到這些索引知識, 對平時的工作中寫出更好更合理的sql會很有幫助。

資料庫建索引的規律

索引是以表列為基礎的資料庫物件。索引中儲存著表中排序的索引列,並且紀錄了索引列在資料庫表中的物理儲存位置,實現了表中資料的邏輯排序。通過索引,可以加快資料的查詢速度和減少系統的響應時間 可以使表和表之間的連線速度加快。但是,不是在任何時候使用索引都能夠達到這種效果。若在不恰當的場合下,使用索引反而會...

如何學習SQL資料庫才能成為高手

我都已經學sql資料庫半年了,但是沒有明顯的進步,請問如何學習sql資料庫才能成為高手?對一張資料表想盡辦法折騰 從搜尋使用者的角度,把各種搜尋方法都實現那麼你的select就很牛x了 接下來就是追求高效率了 在完成上面這位高手的任務以後那麼最好的就是開始練習sql程式設計了。建議你先從最簡單的在w...

資料庫表建索引的經驗

一 哪些情況應該建索引1 表的主鍵 外來鍵必須有索引 2 資料量超過300的表應該有索引 3 經常與其他表進行連線的表,在連線欄位上應該建立索引 4 經常出現在where子句中的字段,特別是大表的字段,應該建立索引 5 索引應該建在選擇性高的字段上 6 索引應該建在小字段上,對於大的文字字段甚至超長...