MySQL 索引與查詢優化

本文介紹一些優化 MySQL 索引設計和查詢的建議。在進行優化工做前,請務必瞭解MySQL EXPLAIN命令: 查看執行計劃html

索引

索引在邏輯上是指從索引列(關鍵字)到數據的映射,經過索引能夠快速的由關鍵字查找到數據記錄。順序查找複雜度爲O(n), 樹狀索引查找複雜度爲O(logn), 哈希索引爲O(1)。算法

MySQL中的索引通常是指BTree索引, InnoDB存儲引擎使用B+樹來實現BTree索引。sql

BTree索引保持數據之間的順序,能夠極大的加快精確搜索(=, in)、範圍搜索(<,>), 去重(DISTINCT), 排序(ORDER BY) 和 聚合(GROUP BY)。數據庫

總結來講使用索引有三個優勢:函數

  • 極大減小了要掃描的數據量
  • 減小排序和臨時表
  • 將隨機IO變爲順序IO

由於寫入數據時須要爲新行創建索引,因此索引會減慢寫入速度。請儘可能避免建立無用的索引oop

索引只能用於獨立的列

示例:性能

SELECT * FROM `user` WHERE `id`=5; -- 可使用索引
SELECT * FROM `user` WHERE `id` + 1 = 5; -- 索引列做爲表達式一部分時沒法使用索引
SELECT * FROM `user` WHERE MD5(first_name)='MD5'; -- 索引列做爲函數參數時沒法使用索引

最左匹配原則

BTree索引具備最左匹配性質, 即只能按照索引列的順序自左向右搜索,不能跳過索引列優化

聯合索引中存在範圍查詢(<, >, like, between) 會致使後面的索引列失效設計

定義表和索引:code

CREATE TABLE `user` (
    `id` INT,
    `first_name` VARCHAR(16),
    `middle_name` VARCHAR(16),
    `last_name` VARCHAR(16),
    PRIMARY KEY (`id`),
    KEY `idx_name` (`first_name`, `middle_name`, `last_name`)
);

示例:

SELECT * FROM `user` WHERE `first_name`='a'; -- 可使用 idx_name 索引
SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b'; -- 可使用 idx_name 索引
SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b' AND `last_name`='c'; -- 可使用 idx_name 索引
SELECT * FROM `user` WHERE `middle_name`='b'; -- 不能使用 idx_name 索引
SELECT * FROM `user` WHERE `middle_name`='b' AND `last_name`='c'; -- 不能使用 idx_name 索引
SELECT * FROM `user` WHERE `first_name`='a' AND `last_name`='c'; -- 不能使用 idx_name 索引

上文中說的"可使用索引"是指能夠用ref,eq_refrange方式進行查詢。

使用 EXPLAIN 命令查看3個不能使用索引示例的執行計劃,能夠發現 type 字段爲 index, 這是在索引樹上進行順序查找。雖然性能優於全表掃描, 但比 ref 和 range 查詢來講要慢不少。

索引列爲字符串等類型時, 可使用索引列的前綴字符串進行模糊查詢

select * from user where first_name = 'abc' AND middle_name like 'de%';

這條語句的類型的爲 range, 即在索引列上進行範圍查詢。

將聯合索引理解爲: 將索引列(關鍵字)按順序拼接, 把拼接後的關鍵字與數據創建映射。最左匹配便是使用關鍵字前綴縮小搜索範圍。

聯合索引

在進行多列搜索時有一條經驗法則: 首先使用選擇性高的列進行搜索

咱們能夠將選擇性定義爲 count(distinct ) / count(*), 也就是說知足條件的數據越少,則條件的選擇性越高。

假設用戶名name比性別gender選擇性高, 那麼查詢應該寫做WHERE name='finley' AND gender='M'而不是WHERE gender='M' AND name='finley'

實際上兩條語句是等效的, 當存在多個查詢條件時 MySQL 優化器會根據索引和選擇性決定最優的過濾順序

爲每個列單獨創建索引,並不能有效支持多列查詢

CREATE TABLE `user` (
    `id` INT,
    `first_name` VARCHAR(16),
    `middle_name` VARCHAR(16),
    `last_name` VARCHAR(16),
    PRIMARY KEY (`id`),
    KEY `idx_first_name` (`first_name`),
    KEY `idx_middle_name` (`middle_name`)
);

查詢語句:

select * from user where first_name = 'a' AND middle_name = 'bc';

查看查詢計劃:

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE user ALL idx_first_name,idx_middle_name NULL NULL NULL 4 100.00 Using where

根據最左匹配法則和優先使用高選擇性列的經驗法則,能夠得出一條建議:

對於須要進行多列查詢的表,應創建包含全部參與查詢列的聯合索引, 索引的順序應按照列的選擇性從強到弱排列

一些關於索引的建議

一般在使用索引檢索到數據以後,須要訪問磁盤上數據表文件讀取所須要的列,這種操做稱爲"回表"。

若索引中包含查詢的全部列,則不須要回表操做直接從索引文件中讀取數據便可, 這種索引稱爲覆蓋索引。

在查詢時儘可能減小"SELECT *"只查詢須要的行, 條件容許時儘可能創建覆蓋索引。

《數據庫索引設計與優化》一書中提出了判斷最佳索引的"三星索引"概念:

  1. 1星: 能夠在索引上(用 ref 或 eq_ref 方式)完成等值查詢。須要取出等值謂詞涉及的列做爲索引開頭的列以知足最左匹配原則。
  2. 2星: 可使用索引進行排序
  3. 3星: 索引中包含要查詢的全部列,不須要回表

MySQL 在索引包含 null 的列時須要額外的開銷, 儘可能避免容許索引列上存在 null

除非有很是嚴格的一致性要求,不然應避免使用外鍵

關於主鍵:

  • 避免使用字符串類型做爲主鍵
  • 使用MD五、UUID等隨機的主鍵可能致使更多的磁盤隨機讀寫,但通常不會有太大的性能問題
  • auto_increment 使用鎖機制實現,可能影響寫入性能。

在查詢較多且業務容許的狀況下, 推薦使用自增主鍵。

不知道放哪兒好的兩條建議:

  • BLOB 用於存儲較大的二進制串,TEXT 用於存儲較大的字符串; 它們不能被索引;
  • ip地址是32位無符號整數,使用 INT UNSIGNED 存儲ip地址而不是字符串。INET_ATON(), INET_NTOA()能夠轉換數字和字符串兩種格式

查詢

一些關於查詢的建議

  • 儘可能避免使用 != 或 not in
  • 條件容許時避免使用 join 查詢, 能夠先分別查詢而後在應用程序內存中關聯
  • 避免在where語句中進行 is null 判斷, 這可能致使MySQL放棄使用索引而進行全表掃描
  • 條件容許時使用 union all 而非 union, 避免 union 沒必要要的去重操做
  • 必要時使用 union (all) 代替 or 條件

小表驅動大表

MySQL在執行多表查詢時能夠採用Nest Loop Join算法,即選擇數據集較小的一張表(數據集)做爲驅動表, 遍歷驅動表中全部記錄並鏈接另外一張表中符合條件的記錄。

在使用 JOIN 進行查詢時 MySQL 會自動選擇數據集較小的一張表做爲驅動表。

LEFT JOIN 強制左表做爲驅動表, RIGHT JOIN 則強制選擇右表做爲驅動表。

MySQL 的 STRAIGHT_JOIN 結果與 INNER JOIN 相同, 但強制使用左表做爲驅動表, 可用來分析選擇不一樣驅動表的效果。

在業務容許的狀況下, 讓 MySQL 自行決定驅動表

在使用 IN 進行多表查詢時通常會把 IN 內部的嵌套循環做爲驅動表, 應儘可能減小IN數據集的大小。實際上, MySQL 也會對 IN 和 EXISTS 查詢進行優化, 選擇最優的驅動表。

相關文章
相關標籤/搜索