本教程關注的重點是 MySQL、MariaDB 和 PerconaDB 數據庫。這些信息也可能與其他數據庫供應商有關,但在某些情況下可能不會。
我應該為我的 SQL 查詢創建哪些索引?
根據通常的經驗規則,當嘗試優化你的 SQL 查詢時,你可以依照以下步驟構建複合索引:
- 首先列出你的查詢中所有使用的表,併為查詢中的每個子查詢創建一個獨立的列表。如果你有一個包含 2 個 SELECT 子查詢的 SELECT 查詢,那你應該建立 3 個列表,每個列表包含引用在其中的表。
- 在此過程結束時,你可能會在每個查詢列表中為每個這些表添加一個列的列表。
- 在你的任何索引中最左邊的列應與查詢相等比較(如 age = 25)中的列匹配。
- 你可以添加多個列,只要所有列與常量進行比較相等即可。
- 那麼,你應該選擇一個列,這將是“範圍列(range column)”。MySQL 在每個索引中只支持一個範圍列。
- 因此,你應該使用範圍運算符(<>, >,
- 將該列給該表添加為你的索引中的下一列。
- 你可以在這裡獲得一些信息 —— 關於在範圍列之前添加相等列的理由(幻燈片由一位 MySQL 優化團隊的成員編寫)。
- 如果查詢中不存在範圍列,你可以添加 GROUP BY 子句中的列。
- 如果查詢中不存在範圍列,並且沒有 GROUP BY 子句,則可以添加 ORDER BY 子句中的列。
- 在某些情況下,創建一個獨立的索引來保存 ORDER BY 子句的列是有意義的,因為 MySQL 有時會選擇使用它。請注意,儘管如此,索引應該包含 ORDER BY 子句中的所有列,它們應該全部在 ORDER BY 子句中用相同的順序(ASC / DESC)指定。這並不能保證數據庫的優化器會選擇這個索引而不是 WHERE 複合索引,但是值得一試。
- 最後,從 SELECT 子句中添加相關的列,這可能允許 MySQL 使用索引作為覆蓋索引。覆蓋索引是包含過濾和查詢子句中的所有列的索引。這樣的索引允許數據庫僅通過使用索引運行查詢,而不需要訪問表。在許多情況下,這種方法顯然更快。
我們通過一個示例來說明:
- SELECT id, first_name, last_name, age from employees
- where first_name = ‘John’ AND last_name = ‘Brack’ and age > 25 ORDER BY age ASC;
對於這個查詢,我們將以添加 first_name 和 last_name 列開始,它們與等號運算符進行比較。然後,我們將添加與範圍條件進行比較的 age 列。這裡不需要在 ORDER BY 子句索引,因為 age 列已經在索引中了。最後同樣重要的是,我們將從 SELECT 子句中添加 id 到索引以生成 covering 索引。
所以為了正確的索引這個查詢,你應該添加以下索引:
employees (first_name, last_name, age, id)
以上是一個非常簡化的偽代碼算法,可以讓你為相當簡單的 SQL 查詢構建簡單的索引。
如果你正在尋找一種方法來實現這個過程的自動化,並希望增強專有索引算法和查詢優化的好處,你可以試用 EverSQL Query Optimizer,它為你做了所有繁重的工作。
索引(或編寫 SQL 查詢)時不應該做什麼?
我們收集了一些程序員和數據庫管理員在編寫查詢和索引表時遇到的最常見的錯誤。
將表中的每一列分別索引
在大多數情況下,MySQL 將不能在查詢中為每個表使用多個索引。
因此,當為表中的每一列創建一個單獨的索引時,數據庫只能使用索引執行其中一個搜索操作,而其餘部分將顯著較慢,因為數據庫不能使用索引執行它們。
建議使用複合索引(本文稍後解釋)而不是單列索引。
filtering 條件中的 OR 運算符
考慮以下查詢語句:
SELECT a, b FROM tbl WHERE a = 3 OR b = 8
在許多情況下,MySQL 將無法使用索引來應用 OR 條件,所以,此查詢是不可索引的。
因此,建議避免這種 OR 條件,並考慮將查詢拆分為兩部分,並結合 UNION DISTINCT 使用(或者最好使用 UNION ALL,以防你不知道其中不會有任何重複的結果)
在索引中列的順序十分重要
比方說,我把我的聯繫人電話簿交給你,電話簿是按照聯繫人的名字排序的,要求你找出電話簿中有多少人名為“John”。你會接過電話簿,說“沒問題”。你將找到包含以 John 開頭的所有名字的頁面,並從此處開始計數。
現在,假設我改變了任務,並給你一個按聯繫人的姓氏排序的電話簿,但要求你仍然統計以“John”作為名字的所有聯繫人。你會怎麼做? 同樣的,數據庫在這種情況下也會很為難的。
現在讓我們看看一個 SQL 查詢來演示使用 MySQL 優化器時相同的行為:
SELECT first_name, last_name FROM contacts WHERE first_name = ‘John’;
擁有索引的聯繫人(first_name, last_name)在這裡是理想的,因為索引從我們的篩選條件開始,然後在 SELECT 子句中以另一個列結束。
但是,具有反向索引的聯繫人(last_name,first_name)是相當沒有意義的,因為數據庫不能使用索引過濾,作為列,我們需要的是索引中的第二個,而不是第一個。
這個例子的結論是,索引中的列順序非常重要。
增加的冗餘索引
當你試圖優化你的 SQL 查詢時,索引是非常有意義的,它可以顯著地提高性能。
但是,這也有不利的一面。你創建的每個索引都應該保持更新,並在數據庫中發生更改時保持同步。因此,對於數據庫中的每個 INSERT / UPDATE / DELETE,都應更新所有相關索引。此更新可能需要較長的時間,特別是對於大型的表/索引。
所以,除非你知道你需要它們,否則不要創建索引。
另外,強烈推薦在某一段時間內分析一下數據庫,搜索任何可以刪除的冗餘索引。
閱讀更多 愛搞機的程序猿 的文章