mysql查詢優化

數據庫查詢優化

Innodb存儲引擎支持以下幾種常見的索引:

  • B+樹索引
  • 全文索引
  • 哈希索引

Innodb存儲引擎支持的哈希索引是自適應的,Innodb存儲引擎會根據表的使用情況自動生成哈希索引,不能認爲干預是否生成哈希索引。

mysql 創建索引和刪除索引方法

索引的創建可以在CREATE TABLE語句中進行,也可以單獨用CREATE INDEX或ALTER TABLE來給表增加索引。刪除索引可以利用ALTER TABLEDROP INDEX語句來實現

alter table table_name add index index_name (column_list) ;
alter table table_name add unique (column_list) ;
alter table table_name add primary key (column_list) ;

其中包括普通索引、UNIQUE索引和PRIMARY KEY索引3種創建索引的格式,table_name是要增加索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。索引名index_name可選,缺省時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE允許在單個語句中更改多個表,因此可以同時創建多個索引。

使用CREATE INDEX語句對錶增加索引。能夠增加普通索引和UNIQUE索引兩種

create index index_name on table_name (column_list) ;
create unique index index_name on table_name (column_list) ;

刪除索引。

刪除索引可以使用ALTER TABLEDROP INDEX語句來實現。DROP INDEX可以在ALTER TABLE內部作爲一條語句處理,其格式如下:

drop index index_name on table_name ;
alter table table_name drop index index_name ;
alter table table_name drop primary key ;

其中,在前面的兩條語句中,都刪除了table_name中的索引index_name。而在最後一條語句中,只在刪除PRIMARY KEY索引中使用,因爲一個表只可能有一個PRIMARY KEY索引,因此不需要指定索引名。如果沒有創建PRIMARY KEY索引,但表具有一個或多個UNIQUE索引,則MySQL將刪除第一個UNIQUE索引。果從表中刪除某列,則索引會受影響。對於多列組合的索引,如果刪除其中的某列,則該列也會從索引中刪除。如果刪除組成索引的所有列,則整個索引將被刪除。

B+樹索引

B+樹索引是真正的索引數據結構,複雜度模型是基於每次相同的操作成本來考慮的,數據庫實現比較複雜,數據保存在磁盤上,而爲了提高性能,每次又可以把部分數據讀入內存來計算,因爲我們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,所以簡單的搜索樹難以滿足複雜的應用場景。
[外鏈圖片轉存失敗(img-yTfvnx2A-1564136718757)(D:/ideaworkspace/zhaoshang/PlatfromGroupShare/source/_posts/mysql/B+.png)]

B+樹查找過程

如圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。

爲什麼要使用B+樹

通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲什麼每個數據項,即索引字段要儘量的小,比如int佔4字節,要比bigint8字節少一半。這也是爲什麼b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。這也就是不用B-樹作爲索引數據結構,因爲B-樹中的的數據項保存的是原始的數據。導致數據項太大,導致樹的高度增高

當b+樹的數據項是複合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因爲建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性

建索引的幾大原則

  • 最左前綴匹配原則,非常重要的原則**,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配**,比如a = 1 and b = 2 and c > 3 and d = 4如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。

  • =和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式

  • 儘量選擇區分度高的列作爲索引,區分度的公式是count(distinctcol)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄

  • 索引列不能參與計算,保持列“乾淨”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);

  • 儘量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。

    B+樹索引種類

  • 聚集索引

  • 輔助索引

INNODB聚集索引

對於表中數據存儲,Innodb存儲引擎採用了聚集的方式,因此每張表中的存儲都是按主鍵的順序進行存放,如果沒有顯式的在定義表時指定主鍵,Innodb存儲引擎會爲每一行生成一個6字節的RowID,並以此作爲主鍵。聚集索引就是按照每張表的主鍵構造一棵B+樹。同時葉子節點中存放的即是整張表的行記錄數據,也將聚集索引的葉子節點稱爲數據頁。聚集索引的這個特性決定了索引組織表中的數據也是索引的一部分由於數據頁只能按照一棵B+樹進行排序,因此每張表只能擁有一個聚集索引。查詢優化器傾向於採用聚集索引,因爲聚集索引能夠在B+樹的葉子節點找到數據。此外,由於定義了數據的邏輯順序,聚集索引能夠特別快針對範圍值的查詢,查詢優化器能夠快速發現某一段範圍的數據頁進行掃描。Innodb採用的是聚集索引。

[外鏈圖片轉存失敗(img-wXVf8OWK-1564136718760)(聚集索引.png)]

聚集索引給人錯覺就是好像按順序連續存儲的,其實聚集索引就是邏輯上按順序連續存儲。物理上並不是。主要有兩點:一是頁是通過雙向鏈表,頁是按照主鍵的順序排序,另一點是每個頁中的記錄也是通過雙向鏈表進行維護的,物理存儲上可以不按照主鍵存儲。

聚集索引的兩大優點

  • 使用Ordey by對記錄進行排序,但是在實際過程中並沒有進行所謂的file sort操作,這就是聚集索引的特點
  • 另一個是範圍查詢,如果要查找主鍵某一範圍內的數據,通過葉子節點的上層節點就可以得到頁的範圍,之後直接讀取數據頁即可。

INNODB輔助索引

對於輔助索引也稱爲非聚集索引,葉子節點並不包含記錄的全部數據,data域存儲相應記錄主鍵的值(這個跟Myisam不一樣的是,myisam存放的是地址而不是主鍵),輔助索引的存在不影響數據在聚集索引中的組織,因此每張表可以有很多個輔助索引,所以當通過輔助索引來尋找數據時,Innodb存儲引擎會遍歷輔助索引通過葉子級別的指針獲得主鍵索引的主鍵,然後再通過主鍵索引引來找到一個完整的行記錄,舉一個簡單的例子:如果在一棵高度爲3的輔助索引樹中查找數據,那需要對這棵輔助索引樹遍歷3次找到指定的主鍵,如果聚集索引樹的高度同樣爲3,那麼還需要還 需要對聚集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,因此一共需要6次邏輯IO訪問以得到一個最終的 數據頁。所以輔助索引的效率遠不如聚集索引(僅限Innodb引擎),所有不建議使用過長的字段作爲主鍵,因爲所有輔助索引都引用主索引,過長的主索引會令索引變得過大,非單調字段作爲主鍵在Innodb不是很好的注意,因爲InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作爲主鍵則是一個很好的選擇。

mysql主鍵生產策略

這裏插一個mysql主鍵生產策略的對比

Sequence ID

數據庫自增序列或字段

優點

  • 簡單、代碼方便,性能可以接受
  • 對分頁或者需要排序的結果很有幫助

缺點

  • 不同數據庫語法和實現不一致,特別數據庫遷移特別麻煩
  • 讀寫分離,一主多從的情況下,只有一個主庫可以生成,單點故障
  • 很難橫向擴展
  • 分庫分表麻煩

優化方案

針對主庫單點,如果有多個master,則每個master庫設置起始數字不一樣,步長不一樣

UUID

利用數據庫或者程序生成,全球唯一

####優點:

  • 簡單方便
  • 全球唯一,遇見數據遷移,系統數據合併,可以快速應對

缺點

  • 沒有排序,無法保證趨勢遞增
  • UUID往往使用字符串存儲,查詢效率低下
  • 存儲空間大
  • 不可讀
  • 傳輸數據量大

優化方案

解決UUID不可讀,可以使用UUID to Int64的方法

GUID

微軟對接UUID這個標準實現的。優缺點同UUID

COMB

通過組合GUID和系統時間,保留UniqueIdentitier的前十個字節,用後6個字節表示GUID生成時間。

優點

  • 解決UUID無序的問題,在其主鍵生成方式提供了Comb算法,保留GUID的10個字節,用另6個字節表示GUID生成的時間。
  • 性能優於UUID

Twitter的snowflake算法

結果是一個long型的ID,核心思想:使用41bit表示毫秒數,10bit表示機器的ID(5個bit表示數據中心,5bit的機器ID),12bit作爲毫秒內的流水好,也就是意味每個節點可以產生4096個ID,最後還有一個符號位,永遠是0

優點

  • 不依賴數據庫,靈活方便,且性能優於數據庫
  • ID按照時間在單機上是遞增的

缺點

  • 單機上是遞增的,但是由於涉及到分佈式環境,每個機器時間有差別,也會出現全局不遞增情況。

Mongdb objectID

MongoDB官方文檔 ObjectID可以算作是和snowflake類似方法,通過“時間+機器碼+pid+inc”共12個字節,通過4+3+2+3的方式最終標識成一個24長度的十六進制字符

Leaf——美團點評分佈式ID生成系統

Myisam索引實現

myisam索引實現和Innodb索引實現差別比較大,myisam索引都是非聚集索引,不論是主鍵還是非主鍵的索引葉子節點的data域都是存放數據記錄的地址
[外鏈圖片轉存失敗(img-4qlQ6Aff-1564136718762)(D:/ideaworkspace/zhaoshang/PlatfromGroupShare/source/_posts/mysql/Myisam_1.png)]
MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。
[外鏈圖片轉存失敗(img-s21z9tZn-1564136718763)(D:/ideaworkspace/zhaoshang/PlatfromGroupShare/source/_posts/mysql/Myisam_2.png)]
所以不論myisam是否是主鍵索引還是非主鍵索引都是輔助索引(不滿足聚集所以的定義)
注意:從上面的圖表中看到堆表(myisam形式)比索引組織方式(innodb)好像要快一點,但是不能忽視對於排序和範圍查找,索引組織表通過B+樹的中間節點就可以找到所有的頁,雖然堆表在離散讀比較有優勢,但是一般數據庫都有實現預讀技來避免多次離散讀操作,所以不存在哪個更好更優的問題,沒有說絕對的好和壞。

哈希索引

哈希算法對字典進行查找,其衝突機制採用鏈表方式,哈希函數採用除法散列方式,對於緩衝池頁的哈希表來說,在緩衝池中page頁都有一個chain指針,它指向相同哈希函數值的頁。對於除法散列,m的取值爲略大於2倍的緩衝池頁數量的質數。

自適應哈希索引

自適應哈希索引經過哈希函數映射到一個哈希表中,因此對於字典型查找速度很快,如:

select * from table  where index_col='xxx'

但是對於範圍查找就無能爲力,範圍查找,是不能使用哈希索引的。

全文索引

select * from blog where content like 'xxx%' --可以使用B+樹索引的
select *  from blog where  content like '%xxx%' --不能使用B+樹索引

全文索引是將存儲於數據庫的整本書或整篇文章中的任意內容信息查找出的技術,他可以根據需要獲得全文有關的文章、節、段、句、詞等信息,也可以進行各種統計和分析。Innodb 1.2x版本開始,Innodb存儲引擎開始支持全文索引檢索。

倒排索引

全文索引通常使用倒排索引來實現,倒排索引利用 輔助表中存儲了單詞與單詞自身在一個或多個文檔中所在位置之間的映射。通常使用關聯數組實現,其擁有兩種表現形式:

  • inverted file index,其表現形式爲{單詞,單詞所在的文檔的ID}
  • full inverted index,其表現形式爲{單詞,(單詞所在文檔的ID,在具體文檔中的位置)}

Innodb存儲引擎從1.2x版本開始支持全文檢索的技術,其採用的是full inverted index的方式,在Innodb存儲引擎中將(documentid,position)視爲一個ilist,因此在全文檢索的表中,有兩個列,一個是word字段,另一個是ilist字段,並且在word字段上設有索引,正如上面所說,倒排索引需要一張輔助表(Auxiliary table),在Innodb存儲引擎中,爲了提高全文檢索的並行性能,共有6張這樣的表。Auxiliary table是持久的表,存放在磁盤上。除此之外,Innodb存儲引擎的全文索引中,還有一個重要的概念:FTS index cache(全文檢索索引緩存)。FTS index cache是一個紅黑樹結構,根據(word,list)進行排序

查詢優化神器 - explain命令

explain詳解

我們會有時會開慢查詢去記錄一些執行時間比較久的SQL語句,找出這些SQL語句並不意味着完事了,到explain這個命令來查看一個這些SQL語句的執行計劃,查看該SQL語句有沒有使用上了索引,有沒有做全表掃描,這都可以通過explain命令來查看。所以我們深入瞭解MySQL的基於開銷的優化器,還可以獲得很多可能被優化器考慮到的訪問策略的細節,以及當運行SQL語句時哪種策略預計會被優化器採用。

explain限制

1. explain根本不會告訴你觸發器、存儲過程或UDF會如何影響查詢
2. 它並不支持存儲過程,儘管手動抽取查詢並單獨地對其進行explain操作
3. 它並 不會告訴你Mysql在查詢的執行計劃中所做的特定優化
4. 它並不會顯示關於查詢的執行計劃的所有信息
5. 它並不區分具有相同名字的事物,例如:它對內存排序和臨時文件都是用“file sort”。
6. 可能會誤導,例如:它會對一個有着很小limit的查詢顯示全索引掃描

慢查詢優化基本步驟

1. 設置SQL_No_cache,運行看看是否真的很慢
2. where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每個字段分別查詢,看哪個字段的區分度最高
3. 利用explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
4. `order by limit` 形式的sql語句讓排序的表優先查
5. 瞭解業務方使用場景
6. 加索引時參照建索引的幾大原則
7. 觀察結果,不符合預期繼續從0分析

索引使用發生的情況

我們可以根據以下語句能夠查到某張表的所有索引。假設條件:我們通過以下語句查詢出來的包含聚集索引: <emp_no, title, from_date>

SHOW INDEX FROM [database].[table];

全列匹配

當按照索引中所有列進行精確匹配(這裏精確匹配指“=”或“IN”匹配)時,索引可以被用到。這裏有一點需要注意,理論上索引對順序是敏感的,但是由於MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引。所以下列兩個語句查詢出來的結果是一模一樣

EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26';
EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer';

最左前綴匹配

當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,所以可以被用到,但是隻能用到一部分,即條件所組成的最左前綴。上面的查詢從分析結果看用到了PRIMARY索引,但是key_len爲4(key_len表示使用的索引長度,是以字節爲單位,由於int型佔用4個字節,而索引中只包含了1列。索引key_len是4),說明只用到了索引的第一列前綴。

mysql 中key_len就算規則

  • 如果列可以爲空,則數據類型佔用的字節的基礎上加1,如果是int型,不能爲空key_len是4,可爲空key_len是5。
  • 如果列是變長,則數據類型佔用的字節的基礎上加2。如Varbinary(10),不能爲空,則key_len=10+2,可以爲空key_len爲10+2+1
  • 如果是字符型,則還需要考慮字符集,如果某列定義是varchar(10),且是utf-8,不能爲空,則key_len爲103+2,可以爲空key_len爲103+2+1
EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001';

參考文章

MySQL索引原理及慢查詢優化
MySQL索引背後的數據結構及算法原理
萬字總結:學習MySQL優化原理,這一篇就夠了
MySQL乾貨之-利用EXPLAIN優化查詢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章