MySQL優化策略小記

MySQL優化

表的優化與列類型選擇

一、表的優化

  1. 定長與變長分離
    如id int, 佔4個字節,char(4)佔4個字符長度,
    即:每一個單元值佔的字節是固定的。核心且常用的字段,宜建成定長,放在一張表中。
    而varchar、text、blob等。這種變長的字段,適合單放一張表中,用主鍵與核心表關聯起來。
  2. 常用字段和不常用字段要分離。
    需要結合網站具體業務來分析字段的查詢場景,查詢頻度低的字段,單拆出來。
  3. 在1對多,需要關聯統計的字段上,添加冗餘字段。

二、列的選擇

  1. 字段類型優先級。整型>date、time>enum,char>varchar>blob、text
    列特點分析:
    • 整型:定長,沒有國家地區之分,沒有字符集差異。比如:tinyint 1、2、3、4、5 <-char(1) a、b、c、d、e, 從空間上都是佔一個字節,但是order by排序,前者更快。因爲後者需要考慮字符集和校對集(就是排序規則)。
    • time:定長,運算快,節省空間。考考慮時區,寫sql時不方便,where ‘2017-09-11’。
    • enum:能起約束值的目的,內部用整型來存儲,但是與char聯查時,內部要經歷串與值的轉化。
    • char:定長,考慮字符集和(排序)校對集。
    • varchar:不定長要考慮字符集的轉換和排序時的校對集,速度慢。
    • text/Blob:無法使用內存臨時表(排序等操作只能在磁盤一進行)。

      如:性別,以utf8爲例
      1. char(1) 3個這字長字節。
      2. enum(‘男’,’女’) 內部轉成數字來存,多了一個轉換過程。
      3. tinyint() 定長一個字節 0、1、2。

  2. 夠用就行,不要慷慨(如smallint, varchar(N))
    大的字段浪費內存影響速度。

    以年齡爲例:tinyint unsigned not null,用存儲到255歲足夠用,用int會浪費三個字節。
    varchar(10)和varchar(100)存儲的內容相同,但在表聯查時,後者會花更多內存。

  3. 儘量避免用NULL()
    NULL不利於索引,要用特殊字節來標註,在磁盤上佔據的空間也更大,mysql5.5已對NULL做改進,但查詢仍然不便

    例如:

    • 可以建立兩張字段相同的表,一個允許爲NULL,一個不允許爲NULL,各自加入一萬條數據,查看索引文件的大小,可以發現爲NULL的索引要大一些。
    • 另外:NULL也不便於查詢
      • where 列名 = null;
      • where 列名 != null; 都查不到值
      • where 列名 is null 或 is not null 才能查到值
  4. Enum列的查詢
    • enum列在內部是用整型來儲存的。
    • enum列與enum列相關聯速度最快。
    • enum列比(var)char 的弱勢:在碰到與char關聯時,要轉化,要花時間; 優勢:當char非常長時,enum仍然是整型固定長度。當查詢量越大時enum優勢越明顯。
    • enum與char/varchar 關聯,因爲要轉化,速度要比enum -> enum、char -> char要慢,但有時也這樣用:數據量特別大時,可以節省IO

索引優化策略

一、索引類型

  1. B-tree 索引
    名叫B-tree索引,大的方面看都用的是平衡樹,但具體實現上,各引擎稍有不同,

比如:嚴格的說,NDB引擎使用的是T-tree,Myisam,InnoDB中默認的是B-tree索引,但抽象一下B-tree系統可理解爲“排好序的快速查找結構”。

B-tree的常見誤區

  • 在where常用的列上都加上索引。例如:where id = 3 and price 100; 查詢id爲3價格大於100的
    • id和price上都加上索引
    • 只能用上id和price索引,因爲是獨立的索引,同時只能使用上1個
  • 在多列上建立索引後,查詢哪個列,索引都將發揮作用
    • 多列索引上,索引發揮作用,需要滿足左前綴要求,以index(a,b,c)爲例(注意只和順序有關),見下表
SQL語句 索引能否發揮作用
where a = 3 a列有用
where a = 3 and b = 4 a、b列都有用
where a = 3 and b = 4 and c = 5 a、b、c 三列都有用
where b = 3 / where c = 4 都沒用
where a = 3 and c = 5 a有用,c沒用
where a = 3 and b > 4 and c = 5 a、b有用,c沒用
同上:where a = 3 and b = like ‘xx%’ and c = 5 a、b有用,c沒用

2. Hash 索引
在memory表中默認的是hash索引,hash的理論查詢時間複雜度爲O(1)。
hash 查詢的缺點:

  • 索引是hash函數計算後的隨機結果,如果是在磁盤上放置數據,比如以id爲例,那麼隨着id增長,id對應的行,在磁盤上隨機放置。
  • 無法對範圍查詢進行優化
  • 無法利於前綴索引。比如,在B-tree中field列的值“helloworld”,並加索引查詢 xx = helloworld,自然可以利用索引 xx = hello 也可以利用索引。因爲hash(“helloworld”)和hash(“hello”),兩者的關係爲僞隨機。
  • 排序也無法優化
  • 必須回行,就是說通過索引只能拿到數據位置,必須要再通過這個地址再回到表中拿數據。

面試題

商品表,有主鍵good_id,欄目 cat_id,價格price; 在價格上加了牽引,按價格查詢時還是很慢,什麼原因,怎麼解決?

答:實際場景中一個電商網站商品分類很多,直接在所有商品中,按價格查詢商品是極少的,一般客戶都是來到分類下再查詢

改正:去掉單獨的price列的索引,加(cat_id, price)複合索引,再查詢; 如果根據日誌統計,發現好多人這樣查:

電腦 –> 品牌 –> 價格 index(cat_id, brand_id, price)

二、非聚簇索引和聚簇索引

  1. 非聚簇索引
    Myisam引擎是使用非聚簇索引
  2. 聚簇索引
    innodb 的主索引文件上,直接存放該行數據,稱爲聚簇索引,次索引指向對主鍵的引用
    myisam 中,主索引和次索引,都指向物理行(磁盤位置)

注意:

  1. 主鍵索引,即存儲索引值,又在葉子中存儲行的數據。
  2. 如果沒有主鍵(primary key),則會 Unique key 做主鍵。
  3. 如果沒有unique key,則系統生成一個內部的rowid 做主鍵。
  4. 像innodb中,主鍵的索引結構中,即存儲了主鍵值,又存儲了行數據,這種結構稱爲“聚簇索引”。

聚簇索引:

  • 優勢:根據主鍵查詢條目較少時,不用回行(數據就在主鍵節點下面)
  • 劣勢:如果碰到不規則數據插入時,造成頻繁的頁分裂

索引覆蓋

索引覆蓋是指:如果查詢的列恰好是索引的一部份,那麼查詢只需要在索引文件上進行,不需要再到磁盤上找數據。這種查詢速度非常快,稱爲“索引覆蓋”。

理想索引

  1. 查詢頻繁
  2. 區分度高:100萬用戶性別上基本男女各50萬,區分度低
  3. 長度小:牽引長度直接影響索引文件大小,影響增刪改的速度,並間接影響查詢速度(佔用內存多)
  4. 儘量能覆蓋常用字段

建索引方法:針對列中的值,從左到右截取部分,來建索引

  • 截的越短,重複度越高,區分度越低,索引效果越不好
  • 截的越長,重複度越低,區分度越高,索引效果越好,帶來影響也越大,增刪改變慢,並間接影響查詢速度。

所以:我們要在 區分度 + 長度 兩者取得平衡

慣用手法:截取不同長度測試其區分度;

例如:
mysql> select count(distinct left(coulm, 6)) / count(*) from table;

索引和排序

排序可能發生的2種情況:

  1. 對於覆蓋索引,直接在索引上查詢時,就是有順序的,using index,在 innodb引擎中,沿着索引字段排序,也有自然排序的,對於myisam引擎,如果按某索引字段排序。

    如id,但取出的字段中,有未索引字段,如goods_name,myisam的做法,不是 索引 -> 回行…
    而是先取出所有行,再進行排序。

  2. 先取出數據,形成臨時表做filesort(文件排序,但文件可能在磁盤上,也可能在內存中)

    • using where:按照字段索引取出的結果,本身就是有序的
    • using filesort:用到文件排序,即取出的結果兩次排序

爭取目標: 取出來的數據本身就是有序的,利用索引來排序。

重複索引和冗餘索引

  1. 重複索引:是指在同一個列,或者順序相同的,幾個列,建立多個索引,稱爲重複索引。其沒任何幫助,只會增大索引文件,拖慢更新速度,去掉。
  2. 冗餘索引:冗餘索引是指2個索引覆蓋的列有重疊。比較常見。

索引碎片和維護

在長期的數據更改過程中,索引文件和數據文件,都將產生空洞,形成碎片。我們可以通過一個nop操作(不產生對數據影響的實質操作)來修改表。

比如:表的引擎爲innodb,可以 alter table xxx engine innodb
optimize table 表名,也可以修復

注意: 修復表的數據及索引碎片,就會把所有的數據文件重新整理一遍,使之對齊,這個過程,如果表的行數比較大,也是非常耗費資源的操作。所以,不能頻繁修復。如果表的update操作很頻繁,可以按周、月來修復。如果不頻繁,可以更長的週期來修復。

explain 列分析

id select_type table type possible_keys key key_len ref rows Extra
查詢編號 simple:不含子查詢 查詢針對的表 all:從表的第一行,逐行掃描,運氣還好可能掃描到最後一行 可能用到的索引 最終使用的索引 使用的索引的最大長度 當連接查詢時,表字段之間的引用關係 估計要掃描多少行 index:用到了索引覆蓋,效率很高
primary:含子查詢或派生查詢 實際表名 index:性能比all稍好,通俗的說:all是掃描所有的數據行,相當於data_all; index是掃描所有的索引節點。相當於 index_all 注意:系統估計可能用到的幾個索引,但最終只能用一個 using where:只靠索引定位不了,不要where判斷一下
subquery:非from子查詢 表的別名 索引覆蓋的情況下能利用上索引數據,但利用不上索引查找,必須全索引掃描。 mysql> select goods_id from goods where goods_id + 1 > 30 using temporary:指用上了臨時表,group by 與 order by不同列時,或group by,order by另的表的列。
derived:from型子查詢 derived:派生表 2.是利用索引進行排序,但是取出所有節點。mysql> select goods_id from goods order by goods_id desc; 分析:沒有加where條件,就得取所有索引節點。同時,又沒有回行,只取索引節點,再排序,經過所有節點 using filesort:文件排序(文件可能在磁盤,也可能在內存)
union null:直接計算,不走表 rang:查詢時能根據索引做範圍掃描 注:如果取出的列包含text,或者更大的如mediumtext等,filesort會發生在磁盤上
union result ref:通過索引列可以直接引用某些數據行 show status like ‘%_table%’ 查詢是否發生在磁盤上
eq ref:通過索引列可以直接引用某一數據行
const、system、null:查詢優化到常量級別,甚至不需要查找時間

in型子查詢引出的陷阱

mysql的查詢優化器,針對in做了優化,被改成了exists子查詢的執行效果,當表越大,查詢越慢。exists子查詢和in子查詢在mysql底層相互轉換。

改進:用連接查詢來代替子查詢。

from子查詢

注意:內層from語句查到的臨時表,是沒有索引的。所以,from的返回內容要儘量小,需要排序,在內層先排好序。

count優化

  1. 誤區:myisam 的count()非常快!
    答:是比較快,但僅限於查詢表的“所有行”比較快,因爲myisam對行數進行了存儲。一旦有條件的查詢,速度就不再快了,尤其是where條件的列上沒有索引。
  2. 假如,id<100的商家都是我們內部測試的,我們想查查真實的商家有多少?

小技巧:

  • select count(id) from table; 快
  • select count(id) from table where id < 100; 快
  • select( (select count(id)from table) - (select count(id) from table where id < 100) ); 快

group by

注意:

  1. 分組用於統計,而更是用於去重的
    不重複的行,分組統計數據,而不要讓查詢產生N多重複數據。group by的列要有索引,可以防止臨時表和文件排序。
    order by 的列要和group by的列一致,否則也會引起臨時表。

  2. 以A、B表連接爲例,主要查詢A表的列
    那麼group by, order by的列儘量相同,而且列應該顯示聲明爲A的列

    例:mysql> select A.id, A.cat_id from inner join B group by A.cat_id order by A.cat_id;

union 優化

union總是要產生臨時表

注意:

  • union的子句條件要儘量具體,即要查詢更少的行
  • 子句的結果在內存時併成結果集,需要去重,去重就要先排序,而加了all之後,不需要去重,union儘量加all

limit 及翻頁優化

limit offset,N 當offset非常大時,效率及低。原因是因爲mysql並不是跳過offset行,然後單取N行,而是取offset+N行,放棄前offset行,返回N行。效率及低,當offset越大,效率越低。

優化辦法:

  1. 從業務上解決:不允許翻過100頁,以百度爲例,一般翻到70頁左右。
  2. 不用offset,用條件查詢

    比如: mysql> select id, name from table limit 10000000, 10; 可以這樣
    mysql> select id, name from table where id > 10000000 limit 10;

  3. 如果數據要作物理刪除,無法用id作爲條件查詢,不要offset精確查詢,還不限制用戶分頁,可以這樣:
    分析:優化思路:不查,少查,查索引,少取列,我們現在必須要查,則只查索引,不查數據,得到id,再用尖支查具體條目,這種技巧就是:延遲關聯

    比如:mysql> select id,name from table inner join (select id from table limit 10000000, 10) as tmp on table.id = tmp.id;

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