掌握 MySQL 的索引查詢優化技巧

本文的內容是總結一些MySQL的常見使用技巧,以供沒有DBA的團隊參考。如無特殊說明,存儲引擎以InnoDB爲準。laravel

MySQL的特色

瞭解MySQL的特色有助於更好的使用MySQL,MySQL和其它常見數據庫最大的不一樣在於存在存儲引擎這個概念,存儲引擎負責存儲和讀取數據。不一樣的存儲引擎具備不一樣的特色,用戶能夠根據業務的特色選擇適合的存儲引擎,甚至是開發一個新的引擎。MySQL的邏輯架構大體以下:面試

MySQL默認的存儲引擎是InnoDB,該存儲引擎的主要特色是:sql

  • 支持事務處理
  • 支持行級鎖
  • 數據存儲在表空間中,表空間由一些列數據文件組成
  • 採用MVVC(多版本併發控制)機制實現高併發
  • 表基於主鍵的聚簇索引創建
  • 支持熱備份

其它常見存儲引擎特色概述:shell

  • MyISAM:老版本MySQL的默認引擎,不支持事務和行級鎖,開發者能夠手動控制表鎖;支持全文索引;崩潰後沒法安全恢復;支持壓縮表,壓縮表數據不可修改,但佔用空間較少,能夠提升查詢性能
  • Archive:只支持Insert和Select,批量插入很快,經過全表掃描查詢數據
  • SCV:把一個SCV文件當作一個表處理
  • Memory:數據存儲在內存中

還有不少,再也不一一列舉。數據庫

數據類型優化

選擇數據類型的原則:緩存

  • 選擇佔用空間小的數據類型
  • 選擇簡單的類型
  • 避免沒必要要的可空列

佔用空間小的類型更節省硬件資源,如磁盤、內存和CPU。儘可能使用簡單的類型,如能用int就不用char,由於後者的排序涉及到字符集的選擇,比使用int複雜。可空列使用更多的存儲空間,若是在可空列上建立索引,MySQL須要額外的字節作記錄。建立表時,默認都是可空,容易被開發者忽視,最好是手動改成不可空,若是要存儲的數據確實不會有空值的話。安全

整型類型

整型類型包括bash

  • tinyint
  • smallint
  • mediumint
  • int
  • bigint

它們分別使用八、1六、2四、32和64位存儲數字,它們能夠表示服務器

範圍的數字,前面能夠加unsigned修飾,這樣可讓正數的可表示範圍提升1倍,可是沒法表示負數。另外,爲整型指定長度沒什麼卵用,數據類型定下來,長度也就相應定下來了。架構

小數類型

  • float
  • double
  • decimal

floatdouble就是一般意義上的floatdouble,前者使用32位存儲數據,後者使用64位存儲數據,和整型同樣,爲它們指定長度沒什麼卵用。

decimal類型比較複雜,支持精確計算,佔用的空間也大,decimal使用每4個字節表示9個數字,如decimal(18,9)表示數字長度是18,其中小數位9個數字,整數部分9個數字,加上小數點自己,共佔用9個字節。考慮到decimal佔用空間較多,以及精度計算很複雜,數據量大的時候能夠考慮用bigint代替之,能夠在持久化和讀取前對真實數據進行一些縮放操做。

字符串類型

  • varchar
  • char
  • varbinary
  • binary
  • blob
  • text
  • 枚舉

varchar類型數據實際佔用空間等於字符串的長度加上1個或2個用來記錄字符串長度的字節(當row-format沒有被設置爲fixed時),varchar很節省空間。當表中某列字符串類型的數據長度差異較大時適合使用varchar。

char的實際佔用空間是固定的,當表中字符串數據的長度相差無幾或很短時適合使用chart類型。

與varchar和char對應的有varbinary和binary,後者存儲的是二進制字符串,和前者相比,後者大小寫敏感,不用考慮編碼方式,執行比較操做時更快。

須要注意的是:雖然varchar(5)和varchar(200)在存儲「hello」這個字符串時使用相同的存儲空間,但並不意味着將varchar的長度設置太大不會影響性能,實際上,MySQL的某些內部計算,好比建立內存臨時表時(某些查詢會致使MySQL自動建立臨時表),會分配固定大小的空間存放數據。

blob使用二進制字符串保存大文本,text使用字符保存大文本,InnoDB會使用專門的外部存儲區來存放此類數據,數據行內僅存放指向他們的指針,此類數據不宜建立索引(要建立也只能正對字符串前綴建立),不過也不會有人這麼幹。

若是某列字符串大量重複且內容有限,可以使用枚舉代替,MySQL處理枚舉時維護了一個「數字-字符串」表,使用枚舉能夠減小不少存儲空間。

時間類型

  • year
  • date
  • time
  • datetime
  • timestamp

datetime存儲範圍是1001到9999,精確到秒。timestamp存儲1970年1月1日午夜以來的秒數,能夠表示到2038年。佔用4個字節,是datetime佔用空間的一半。timestamp表示的時間和時區有關,另外timestamp列還有個特性,執行insert或update語句時,MySQL會自動更新第一個類型爲timestamp的列的數據爲當前時間。不少表中都有設計有一列叫作UpdateTime,這個列使用timestamp卻是挺合適的,會自動更新,前提是系統不會使用到2038年。

主鍵類型的選擇

儘量使用整型,整型佔用空間少,還能夠設置爲自動增加。尤爲別使用GUID,MD5等哈希值字符串做爲主鍵,這類字符串隨機性很大,因爲InnoDB主鍵默認是聚簇索引列,因此致使數據存儲太分散。另外,InnoDB的二級索引列中默認包含主鍵列,若是主鍵太長,也會使得二級索引很佔空間。

特殊類型的數據

存儲IP最好使用32位無符號整型,MySQL提供了函數inet_aton()和inet_ntoa()進行IP地址的數字表示和字符串表示之間的轉換。

索引優化

InnoDB使用B+樹實現索引,舉個例子,假設有個People,建表語句以下

CREATE TABLE `people` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Name` varchar(5) NOT NULL,
  `Age` tinyint(4) NOT NULL,
  `Number` char(5) NOT NULL COMMENT '編號',
  PRIMARY KEY (`Id`),
  KEY `i_name_age_number` (`Name`,`Age`,`Number`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
複製代碼

插入數據:

它的索引結構大體是這樣的:

也就是說,索引列的順序很重要,若是兩行數據的Name列相同,則用Age列比較大小,若是Age列相同,則用Number列比較大小。先用第一列排序,而後是第二列,最後是第三列。

查詢的使用應該儘可能從左往右匹配,另外,若是左邊列範圍查找,右邊列沒法使用索引;還有就是不能隔列查詢,不然後面的索引也沒法使用到。如如下幾個SQL是正面範例:

  • SELECT * from people where Name =’Abel’ and Age = 2 AND Number = 12312
  • SELECT * from people where Name =’Abel’
  • SELECT * from people where Name like ‘Abel%’
  • SELECT * from people where Name = ‘Andy’ and Age BETWEEN 11 and 20
  • SELECT * from people ORDER BY NAME
  • SELECT * from people ORDER BY NAME, Age
  • SELECT * from people GROUP BY Name

如下幾個SQL是反面範例:

  • SELECT * from people where Age = 2
  • SELECT * from people where NAME like ‘%B’
  • SELECT * from people where age = 2
  • SELECT * from people where NAME = ‘ABC’ AND number = 3
  • SELECT * from people where NAME like ‘B%’ and age = 22

一個使用Hash值建立索引的技巧

若是表中有一列存儲較長字符串,假設名字爲URL,在此列上建立的索引比較大,有個辦法能夠緩解:建立URL字符串的數字哈希值的索引。再新建一個字段,好比叫作URL_CRC,專門放置URL的哈希值,而後給這個字段建立索引,查詢時這樣寫:

select * from t where URL_CRC = 387695885 and URL = 'www.baidu.com'

若是數據量比較多,爲防止哈希衝突,可自定義哈希函數,或用MD5函數返回值的一部分做爲哈希值:

SELECT CONV(RIGHT(MD5('www.baidu.com'),16), 16, 10)

前綴索引

若是字符串列存儲的數據較長,建立的索引也很大,這時可使用前綴索引,即:只針對字符串前幾個字符作索引,這樣能夠縮短索引的大小,不過,顯然,此類索引在執行order bygroup by時不起做用。

建立前綴索引時選擇前綴長度很重要,在不破壞原來數據分佈的狀況下儘量選擇較短的前綴。舉個例子,若是若是大部分字符串是以」abc」開頭,那麼若是限定前綴索引長度爲4,索引值會包含太多的重複的」abcX」。

多列索引

上面提到的「People」上建立的索引即爲多列索引,多列索引每每比多個單列索引更好。

  • 對多個索引進行and查詢時,應該建立多列索引,而不是多個單列索引
  • 能夠試試這樣寫的效果:

select * from t where f1 = 'v1' and f2 <> 'v2' union all select * from t where f2 = 'v2' and f1 <> 'v1'

多列索引的順序很重要,一般,不考慮排序和分組查詢時,應該把選擇性(選擇性是指某表索引列不一樣數據的個數/總行數。選擇性高意味着重複數據少)大的列放到前面。但也有例外,若是能確認某些查詢是頻繁執行的,則應該優先照顧這些查詢的選擇性,好比,若是上面的People表中Name的選擇性大於Age,查詢語句應該這樣寫:

select * from people where name = 'xxx' and age = xx

Name列放了索引中的左側比較合適,可是若是某個SQL執行的評率最高,好比

select * from people where name = 'xxx' and age = 20

當age=20的記錄在數據庫中很是少時,反而把age放到索引列的左端效率更高。把age放了索引左端可能對其它age不等於20的查詢來講不公平,若是不能肯定age=20是最很是頻繁的查詢條件,仍是要綜合考慮,把name放了左側合適。

聚簇索引

聚簇索引是一種數據存儲結構,InnoDB在主鍵的索引的葉子節點中直接保存了數據行,而不是像二級索引那樣只是保存了索引列的值和所指向行的主鍵值。因爲這個特性,一個表只能有一個聚簇索引。若是一個表沒有定義主鍵也沒有定義具備惟一索引的列,那麼InnoDB會生成一個隱藏列,而且在此列設爲聚簇索引列。

覆蓋索引

簡單地說,某些查詢只須要查詢索引列,那麼就不用再根據索引B樹節點記錄的主鍵ID進行二次查詢了。

重複索引和冗餘索引

若是重複在某列建立索引,並不會帶來任何好處,只有壞處,應該儘可能避免。好比給主鍵建立惟一索引和普通索引就是多於的,由於InnoDB的主鍵默認就是聚簇索引了。

冗餘索引和重複索引不一樣,好比某個索引是(A,B),另外一個索引是(A),這叫冗餘索引,前者能夠代替後者,後者不能夠代替前者的做用。可是(A,B)和(B)以及(A,B)和(B,A)不算冗餘索引,起做用誰也代替不了誰。

若是一個表中已經存在索引(A),如今又想建立索引(A,B),那麼只需擴展就的索引就能夠,沒有必要建立新的索引。須要注意的是若是已經存在索引(A),那麼也沒有必要在建立索引(A,ID),其中ID指主鍵,由於索引A默認已經包含了主鍵了,也算是冗餘主鍵。

可是,有時候,冗餘索引也是可取的,假設已經存在索引(A),將其擴展爲(A,B)後,由於B列是一個很長的類型,致使用A單獨查詢時沒有之前快了,這時能夠考慮新建立索引(A,B)。

不使用的索引

不使用的索引徒然增長insert、update和delete的效率,應該及時刪除

索引使用總結

索引的三星原則:

  • 索引將查詢相關的記錄按順序放在一塊兒則得一星
  • 索引中的數據順序和查詢結果的排序一致則得一星
  • 索引中包含了查詢所須要的所有列則得一星

第一個條原則的意思是where條件中查詢的順序和索引是一致的,就是前面說的從左到右使用索引。

索引不是萬能的,當數據量巨大時,維護索引自己也是耗費性能的,應該考慮分區分表存儲。

查詢優化

查詢慢的緣由

是否向數據庫請求了多餘的行

好比應用程序只須要10條數據,可是卻向數據庫請求了全部的數據,在顯示在UI上以前拋棄了大部分數據。

是否向數據庫請求了多餘的列

好比應用程序只須要展示5列,但卻經過select * from 把所有的列都查了出來

是否重複屢次執行了相同的查詢

應用程序是否能夠考慮一次查詢而後緩存,後面的用到時可使用第一次查詢出來的記錄。

MySQL是否在掃描額外的記錄

經過查看執行計劃能夠大概瞭解須要掃描的記錄數,若是這個數字超出了預期,儘量經過添加索引、優化SQL(就是本節的重點),或者改變表結構(如新增一個單獨的彙總表,專門供某個語句查詢用)來解決。

重構查詢的方式

  • 將一個複雜的查詢分解成多個簡單的查詢
  • 將大的查詢切分紅小的查詢,每次查詢功能同樣,只完成一小部分
  • 分解關聯查詢。能夠將一個大的關聯查詢改爲分別查詢若干個表,而後在應用程序代碼中處理

雜七雜八

優化count()

Count有兩個做用,一是統計指定的列或表達式,二是統計行數。若是參數傳入一列名或者是一個表達式,那麼count會統計全部結果不爲NULL的行數,若是參數是*,那麼count會統計全部行數。這裏有一個傳表達式的例子:

SELECT count(name like 'B%') from people

  • 可使用近似值優化來代替count(),如執行計劃中的行數。
  • 索引覆蓋掃描
  • 增長彙總表
  • 增長內存緩存系統記錄數據條數


關聯查詢的優化

  • MySQL優化器關聯表查詢是這樣進行的,好比有兩個表A和B經過c列關聯,MySQL會遍歷A表,而後根據遍歷到的c列的值去B表中查找數據。綜上所述,一般,如無只須要給B表的c列加上索引便可
  • 確保order by和group by涉及到的列只屬於一個表,這樣纔有可能發揮索引的做用


優化子查詢

對於MySQL5.5及如下版本,儘可能用鏈接代替子查詢。

優化group by、distinct

若是可能,儘可能對主鍵施加這兩種操做。

優化limit,好比有SQL

SELECT * from sa_stockinfo ORDER BY StockAcc LIMIT 400, 5
複製代碼

MySQL優化器會查找405行全部列數據而後丟棄400。若是能利用覆蓋索引查詢則沒必要查詢出這麼多列,先修改成:

SELECT * FROM sa_stockinfo i JOIN (SELECT StockInfoID FROM sa_stockinfo ORDER BY StockAcc LIMIT 400,5)t ON i.StockInfoID = t.StockInfoID
複製代碼

StockAcc上建有索引,該查詢會利用索引覆蓋,較快找出符合條件的主鍵,而後在作聯合查詢,在數據量大的時候效果明顯。

優化union

如無必要,必定要用關鍵字 union all,這樣MySQL把數據放到臨時表時不會再作惟一性驗證

判斷某條記錄是否存在,一般的作法是

select count(*) from t where condition
複製代碼

最好這樣寫:

SELECT IFNULL((SELECT 1 from tableName where condition LIMIT 1),0)複製代碼
以上內容但願幫助到你們, 不少PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提高,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨須要的能夠免費分享給你們 ,須要戳這裏 PHP進階架構師>>>視頻、面試文檔免費獲取
相關文章
相關標籤/搜索