1. 程式人生 > 資訊 >育碧宣佈 Ubisoft Quartz 專案,正式進軍元宇宙及 NFT

育碧宣佈 Ubisoft Quartz 專案,正式進軍元宇宙及 NFT

寫在前面

一直有想要了解資料庫索引相關問題的想法,但是以前用不著,也就只是瞭解了個大概(懶)。前幾天在工作中遇到了重複索引的情況,經過導師的諄諄教誨,這才下定心要搞清楚。

本文內容大部分來自網路蒐集,原文出處太多不便一一列出,請見諒,在此感謝各位網路先驅!

0x00 什麼是資料庫索引

資料庫這個東西就不必多言,資料庫索引就是對資料庫表單當中的一列或者多列的值進行排序的一種結構,通過資料庫索引我們能加快對錶中資料的檢索。當我們建立了一個索引之後,查詢資料時就能夠從索引中直接定位到相應的資料。舉個栗子:當我們沒有對錶中的欄位建立索引或者我們的查尋語句的條件沒有有效地命中索引時,想要找到一個數據最壞的情況需要遍歷全表。但是如果我們建立了合適的索引,並且編寫 sql 語句時有效地命中了索引,這樣就可以查詢索引的方式查詢到想要的資料,因為索引的查詢演算法是經過優化與實驗的,比我們直接去遍歷資料庫要快得多。

01 索引的分類

唯一索引: 唯一索引類似於資料庫欄位中的主鍵,它是互斥的,決不允許一個表中出現唯一索引的欄位值相同的情況。例如:在一張姓名錶中的“姓”一列加上唯一索引,那麼整張表中就不允許出現同姓的情況。

主鍵索引: 主鍵索引是唯一索引的特殊情況,我們知道一張資料庫表的主鍵本來就是不允許重複的,它用於特定的標識資料庫中的一行。主鍵索引在建立資料庫表指定主鍵時就已經自動的建立好了,不需要程式設計師去手動建立

常規索引: 這是最常用的資料庫索引型別,它適用於除主鍵之外的所有欄位,在欄位屬性中不存在唯一性約束時,記錄中的值允許重複。

索引組: 索引的建立並沒有要求必須是單個欄位,我們同樣可以使用多個欄位來建立索引組,比如:INDEX name ("姓", "名")

0x01 怎麼建立一個合適的索引

上面瞭解了,資料庫的索引是什麼,那麼要怎麼才能建立一個實用的索引就是接下來要考慮的事情了。

01 重複索引與冗餘索引

這兩者類似但不完全相同,索引跟資料庫表的欄位一樣,同一張表中不允許出現兩個相同名稱的索引,但不對索引中的欄位進行約束,所以在程式設計師沒注意檢查的情況下,就可能會出現建立了重複索引或者冗餘索引的情況。

重複索引: 當一張表中出現 index1 (x) , index2 (x) 的索引時,index1 和 index2 就被稱為重複索引,這種情況除了會增加資料庫儲存負擔外,並不能提高資料庫的查詢效率。

冗餘索引: 冗餘索引比較起重複索引來講更加的複雜,它涉及到一個概念——左字首索引,即一個索引組當中位於第一位的索引列的欄位名,如:index (a, b, c) 中 “a” 就是該索引組當中的左字首索引。只有當兩個索引當中的左字首索引重複時,才是冗餘的,如:已經存在 index (a),此時 index (a,b) ,那麼第二個索引就叫做冗餘索引。在建立索引時,儘量在原有索引的基礎上進行拓展,而不是去新增,新增索引能夠使我們的索引能夠覆蓋到更多的查詢情況,但是過多的索引也必然會帶來記憶體的消耗和增刪改效能下降。但這並非絕對,當原有索引已經過於龐大時,新建一個索引更有利於效能。是增還是拓展,這個要交給程式設計師自己來衡量

02 如何判斷是否應該新建索引

  1. 頻繁作為查詢條件的欄位應該為其建立索引

    建立索引能夠極大地提高資料庫在進行 select 時的 IO 速度,當一個欄位在專案中被頻繁的使用到 select 當中時,理應為其建立索引。

  2. 唯一性太差的欄位不適合單獨為其建立索引

    一個欄位的唯一性指的是它儲存的資料是否區別於同欄位的其他記錄,如:一個表單中存在 “性別” 這一欄位,它儲存的資料就只能有 “男性” 和 “女性” 兩種,這時我們稱 “性別” 這個欄位的唯一性很差。如果我們為這些唯一性很差的欄位建立索引,當資料庫查詢索引時,同一個索引下面可能對應非常多的記錄,這時儲存引擎在訪問資料時會帶來大量的隨機 IO ,甚至有時會出現大量的重複 IO 。

    隨機 IO 很好理解,資料庫根據索引查詢資料產生的是隨機 IO ,走全表掃描時產生的是順序 IO ,從效能上比較起來,順序 IO 的效能要遠勝於隨機 IO 。 重複 IO 的產生源於資料庫中儲存的資料是以資料頁的方式儲存,同一個表中的資料存在在不同的頁當中,當我們查詢唯一性很差的欄位(A,B)時,首先會去查詢 A 欄位對應的資料,這些資料可能存在不同的資料頁當中,資料庫在一張頁當中尋找完成 A 對應的資料後會丟棄當前頁並載入下一頁繼續查詢,直到 A 的資料查詢完成。在這之後會去查詢 B 的結果,如果 B 的資料存在於 A 已經丟棄的資料頁當中,那麼儲存引擎又會把它載入回來,這時就發生了重複 IO。簡單來說就是:同一次查詢當中,任意兩個欄位的查詢載入了對方已經丟棄的資料頁,從而產生的 IO ,就是重複 IO

  3. 更新太過於頻繁的欄位不適合為其建立索引

    這個命題是相對的,怎麼才叫過於頻繁呢?(查詢:更新 = 1:1)算頻繁還是(查詢:更新 = 200:1)才算呢,這個需要自行取捨。

  4. 不出現在 WHERE 中的欄位不應該建立索引

0x02 如何有效地使用索引

在資料庫中建立的索引並不是一旦建立就一定會被查詢語句所使用的,合理的去構造爭取且有效地查詢語句才能更好的發揮索引帶來的效能提升。

上面說到,索引並不是越多越好,過多的索引會增加資料庫的體積,增加資料庫維護索引的開銷。那麼如何使用更少的索引發揮更大的作用,就是查詢語句需要考慮事情了。

  • 常規 sql
index (col1, col2, col3)
-- 如果構造的查詢語句能夠命中到索引組的左字首索引,那麼資料庫在查詢資料時就會使用到這條索引,反之則不會
SELECT * FROM tb WHERE  col1 = val1   -- can_use
SELECT * FROM tb WHERE  col1 = val1 and col2 = val2  -- can_use

SELECT * FROM  tb WHERE  col3 = val3   -- can_not_use
SELECT * FROM  tb  WHERE  col2 = val2   -- can_not_use
SELECT * FROM  tb  WHERE  col2 = val2  and  col3=val3  -- can_not_use
  • 除了上述的正常編寫查詢語句的情況外,還有使用萬用字元和不等於操作符的情況
-- 使用萬用字元
SELECT * FROM tb WHERE  col1 = %num	-- can_not_use
SELECT * FROM tb WHERE  col1 = %num%	-- can_not_use

SELECT * FROM tb WHERE  col1 = num%	-- can_use

-- 使用不等於操作符(<>, !=),當查詢語句中使用到了不等於操作符時,不管是否已經為相應欄位建立索引,資料庫都會走一次全表掃描,但可以通過 or 來避免這種情況
SELECT * FROM tb where col1 <> 1000 -> SELECT * FROM tb where col1 < 1000 or col1 > 1000

  • 使用函式
-- 當函式處理的是表單欄位時,無法使用索引
select * from staff where trunc(birthdate) = '01-MAY-82'  -- can_not_use

-- 當函式處理的是條件時,可以使用索引
select * from staff where birthdate < (to_date('01-MAY-82') + 0.9999)  -- can_use
  • 資料型別不匹配的情況,這個不必多說,不報錯就不錯了,查詢時一定要注意保持查詢條件與欄位的資料型別一致。