MySQL 底層原理+優化


一、索引的底層數據結構與算法

1、什么是索引?

  索引是幫助MySQL高效獲取數據的排好序的數據結構。

2、索引的數據結構  

  1. B+Tree(B-Tree變種)

    1. 非葉子節點不存儲data,只存儲索引(冗余),可以放更多的索引
    2. 葉子節點包含所有索引字段

    3. 葉子節點用指針連接,提高區間訪問的性能

  2. MyISAM索引文件和數據文件是分離的(非聚集)

  3. InnoDB索引實現(聚集)

    1. 表數據文件本身就是按B+Tree組織的一個索引結構文件
    2. 葉節點包含了完整的數據記錄

    3. 建議InnoDB表必須建主鍵,並且推薦使用整型的自增主鍵
    4. 為什么非主鍵索引結構葉子節點存儲的是主鍵值?(一致性和節省存儲空間)


  4. 聯合索引數據結構

     

二、Explain工具使用

1、explain? 

  在 select 語句之前增加 explain 關鍵字,MySQL 會在查詢上設置一個標記,執行查詢會返回執行計划的信息,而不是執行這條SQL

  1. explain extended+show warnings:會在 explain 的基礎上額外提供一些查詢優化的信息。緊隨其后通過 show warnings 命令可以得到優化后的查詢語句
  2. explain partitions相比 explain 多了個 partitions 字段,如果查詢是基於分區表的話,會顯示查詢將訪問的分區。

2、explain中的列

 

 

  • id:

有幾個 select 就有幾個id,並且id的順序是按 select 出現的順序增長的。id列越大執行優先級越高,id相同則從上往下執行,id為NULL最后執行。

  • select_type:

對應行是簡單還是復雜的查詢

    1. simple:簡單查詢。查詢不包含子查詢和union
    2. primary:復雜查詢中最外層的 select
    3. subquery:包含在 select 中的子查詢(不在 from 子句中)
    4. derived:包含在 from 子句中的子查詢。MySQL會將結果存放在一個臨時表中,也稱為派生表(derived的英文含義)
    5. union:在 union 中的第二個和隨后的 select

 

  • table這一列表示 explain 的一行正在訪問哪個表。

  • type:

  • system > const > eq_ref > ref > range > index > ALL,一般來說要保證達到range級別

    1. const, system:mysql能對查詢的某部分進行優化並將其轉化成一個常量(可以看show warnings 的結果)。
    2. eq_ref:primary key 或 unique key 索引的所有部分被連接使用 ,最多只會返回一條符合條件的記錄。
    3. ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前綴,索引要和某個值相比較,可能會找到多個符合條件的行。
    4. range:范圍掃描通常出現在 in(), between ,> ,<, >= 等操作中。使用一個索引來檢索給定范圍的行。
    5. index:掃描全索引就能拿到結果,一般是掃描某個二級索引,這種掃描不會從索引樹根節點開始快速查找,而是直接對二級索引的葉子節點遍歷和掃描,速度還是比較慢的,這種查詢一般為使用覆蓋索引,二級索引一般比較小,所以這種通常比ALL快一些。
    6. ALL:即全表掃描,掃描你的聚簇索引的所有葉子節點。通常情況下這需要增加索引來進行優化了。
  • possible_keys:顯示查詢可能使用哪些索引來查找。
  • key:實際采用哪個索引來優化對該表的訪問。
  • key_len:通過這個值可以算出具體使用了索引中的哪些列。
    • 字符串:char(n):3n ,  varchar(n): 3n + 2 ,加的2字節用來存儲字符串長度,因為varchar是變長字符串
    • 數值類型:tinyint:1  smallint:2  int:4  bigint:8字節
    • 時間類型:date:3  timestamp:4  datetime:8
    如果字段允許為 NULL,需要1字節記錄是否為 NULL索引最大長度是768字節,當字符串過長時,mysql會做一個類似左前綴索引的處理,將前半部分的字符提取出來做索引。
  • ref:這一列顯示了在key列記錄的索引中,表查找值所用到的列或常量,常見的有:const(常量),字段名(例:film.id)
  • rows:這一列是mysql估計要讀取並檢測的行數,注意這個不是結果集里的行數。
  • Extra:這一列展示的是額外信息。常見的重要值如下:
  1. Using index:使用覆蓋索引
  2. Using where:使用 where 語句來處理結果,並且查詢的列未被索引覆蓋
  3. Using index condition:查詢的列不完全被索引覆蓋,where條件中是一個前導列的范圍;
  4. Using temporary:mysql需要創建一張臨時表來處理查詢。出現這種情況一般是要進行優化的,首先是想到用索引來優化。
  5. Using filesort:將用外部排序而不是索引排序,數據較小時從內存排序,否則需要在磁盤完成排序。這種情況下一般也是要考慮使用索引來優化的。
  6. Select tables optimized away:使用某些聚合函數(比如 max、min)來訪問存在索引的某個字段是

 

3、優化建議 

 

三、bin-log歸檔

1、如果誤刪了數據庫,可以使用bin-log進行歸檔

  • Binlog在MySql的Server層實現
  • bin-log為邏輯日志,記錄每條語句的原始邏輯
  • bin-log不限大小,追加寫入,不會覆蓋以前日志

2、開啟bin-log

#配置開啟binlog,my.conf
log‐bin=/usr/local/mysql/data/binlog/mysql‐bin
#注意5.7以及更高版本需要配置本項:
server‐id=123454(自定義,保證唯一性);
#binlog格式,有3種statement,row,mixed
binlog‐format=ROW
#表示每1次執行寫入就與硬盤同步,會影響性能,為0時表示,事務提交時mysql不做刷盤操作,由系統決定
sync‐binlog=1 

bin-log 命令

1 mysql> show variables like '%log_bin%'; 查看bin‐log是否開啟 
2 mysql> flush logs; 會多一個最新的bin‐log日志 
3 mysql> show master status; 查看最后一個bin‐log日志的相關信息 
4 mysql> reset master; 清空所有的bin‐log日志

查看bin-log內容

1 mysql> /usr/local/mysql/bin/mysqlbinlog ‐‐no‐defaults /usr/local/mysql/data/binlog/mysql‐bin. 000001 查看binlog內容

數據歸檔操作

1 從bin‐log恢復數據  
2 恢復全部數據 
3 /usr/local/mysql/bin/mysqlbinlog ‐‐no‐defaults /usr/local/mysql/data/binlog/mysql‐bin.000001 |mysql ‐uroot ‐p tuling(數據庫名) 
4 恢復指定位置數據 
5 /usr/local/mysql/bin/mysqlbinlog ‐‐no‐defaults ‐‐start‐position="408" ‐‐stop‐position="731" /usr/local/mysql/data/binlog/mysql‐bin.000001 |mysql ‐uroot ‐p tuling(數據庫) 
6 恢復指定時間段數據 
7 /usr/local/mysql/bin/mysqlbinlog ‐‐no‐defaults /usr/local/mysql/data/binlog/mysql‐bin.000001 ‐‐stop‐date= "2018‐03‐02 12:00:00" ‐‐start‐date= "2019‐03‐02 11:55:00"|mysql ‐uroot ‐p test(數 據庫)

四、性能調優

1、索引設計原則

1、代碼先行,索引后上

  不知大家一般是怎么給數據表建立索引的,是建完表馬上就建立索引嗎?這其實是不對的,一般應該等到主體業務功能開發完畢,把涉及到該表相關sql都要拿出來分析之后再建立索引。

2、聯合索引盡量覆蓋條件

  比如可以設計一個或者兩三個聯合索引(盡量少建單值索引),讓每一個聯合索引都盡量去包含sql語句里的where、order by、group by的字段,還要確保這些聯合索引的字段順序盡量滿足sql查詢的最左前綴原則。

3、不要在小基數字段上建立索引

  索引基數是指這個字段在表里總共有多少個不同的值,比如一張表總共100萬行記錄,其中有個性別字段,其值不是男就是女,那么該字段的基數就是2。如果對這種小基數字段建立索引的話,還不如全表掃描了,因為你的索引樹里就包含男和女兩種值,根本沒
法進行快速的二分查找,那用索引就沒有太大的意義了。一般建立索引,盡量使用那些基數比較大的字段,就是值比較多的字段,那么才能發揮出B+樹快速二分查找的優勢來。

4、長字符串我們可以采用前綴索引

  盡量對字段類型較小的列設計索引,比如說什么tinyint之類的,因為字段類型較小的話,占用磁盤空間也會比較小,此時你在搜索的時候性能也會比較好一點。當然,這個所謂的字段類型小一點的列,也不是絕對的,很多時候你就是要針對varchar(255)這種字段建立
索引,哪怕多占用一些磁盤空間也是有必要的。對於這種varchar(255)的大字段可能會比較占用磁盤空間,可以稍微優化下,比如針對這個字段的前20個字符建立索引,就是說,對這個字段里的每個值的前20個字符放在索引樹里,類似於 KEY
index(name(20),age,position)。此時你在where條件里搜索的時候,如果是根據name字段來搜索,那么此時就會先到索引樹里根據name字段的前20個字符去搜索,定位到之后前20個字符的前綴匹配的部分數據之后,再回到聚簇索引提取出來
完整的name字段值進行比對。但是假如你要是order by name,那么此時你的name因為在索引樹里僅僅包含了前20個字符,所以這個排序是沒法用上索引的, group by也是同理。所以這里大家要對前綴索引有一個了解。

5、where與order by沖突時優先where

  在where和order by出現索引設計沖突時,到底是針對where去設計索引,還是針對order by設計索引?到底是讓where去用上索引,還是讓order by用上索引?一般這種時候往往都是讓where條件去使用索引來快速篩選出來一部分指定的數據,接着再進行排序。
因為大多數情況基於索引進行where篩選往往可以最快速度篩選出你要的少部分數據,然后做排序的成本可能會小很多。

6、基於慢sql查詢做優化

  可以根據監控后台的一些慢sql,針對這些慢sql查詢做特定的索引優化。 

2、索引下推(like'Lilei%')

 可以在索引遍歷過程中,對索引中包含的所有字段先做判斷,過濾掉不符合條件的記錄之后再回表,可以有效的減少回表次數。

  為什么范圍查找Mysql沒有用索引下推優化? 

 是Mysql認為范圍查找過濾的結果集過大,like KK% 在絕大多數情況來看,過濾后的結果集比較小,所以這里Mysql選擇給 likeKK% 用了索引下推優化,當然這也不是絕對的,有時like KK% 也不一定就會走索引下推。 

 

3、Order by與Group by優化

1、優化例子

  1. 利用最左前綴法則:中間字段不能斷,因此查詢用到了name索引,從key_len=74也能看出,age索引列用在排序過程中,因為Extra字段里沒有using filesort
  2.  從explain的執行結果來看:key_len=74,查詢使用了name索引,由於用了position進行排序,跳過了age,出現了Using filesort。

  3. 查找只用到索引name,age和position用於排序,無Using filesort。
  4. 和Case 3中explain的執行結果一樣,但是出現了Using filesort,因為索引的創建順序為name,age,position,但是排序的時候age和position顛倒位置了。 
  5. 與Case 4對比,在Extra中並未出現Using filesort,因為age為常量,在排序中被優化,所以索引未顛倒,不會出現Using filesort
  1.  雖然排序的字段列與索引順序一樣,且order by默認升序,這里position desc變成了降序,導致與索引的排序方式不同,從而產生Using filesort。Mysql8以上版本有降序索引可以支持該種查詢方式。
      
  2. 對於排序來說,多個相等條件也是范圍查詢

     

     

  3. 可以用覆蓋索引優化 

     

     

2、優化總結

  1、MySQL支持兩種方式的排序filesort和index,Using index是指MySQL掃描索引本身完成排序。index效率高,filesort效率低。
  2、order by滿足兩種情況會使用Using index。
    1) order by語句使用索引最左前列。
    2) 使用where子句與order by子句條件列組合滿足索引最左前列。
  3、盡量在索引列上完成排序,遵循索引建立(索引創建的順序)時的最左前綴法則。
  4、如果order by的條件不在索引列上,就會產生Using filesort。
  5、能用覆蓋索引盡量用覆蓋索引
  6、group by與order by很類似,其實質是先排序后分組,遵照索引創建順序的最左前綴法則。對於groupby的優化如果不需要排序的可以加上order by null禁止排序。注意,where高於having,能寫在where中的限定條件就不要去having限定了。 
 

4、分頁查詢優化

  很多分頁查詢的sql實現 ,看似只查詢了 10 條記錄,實際這條 SQL 是先讀取 10010條記錄,然后拋棄前 10000 條記錄,然后讀到后面 10 條想要的數據。因此要查詢一張大表比較靠后的數據,執行效率是非常低的。 

 mysql> select * from employees limit 10000,10;

  常見技巧:

  1、根據自增且連續的主鍵排序的分頁查詢
  

   滿足以下兩個條件:

    • 主鍵自增且連續
    • 果是按照主鍵排序的 

 

  2、根據非主鍵字段排序的分頁查詢    

select * from employees ORDER BY name limit 90000,5;  (沒有使用name索引)
select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id; (使用了索引,並且時間減少了一半以上)

  3、Join關聯查詢優化 
    3.1 標關聯常見的兩種算法      

      • 嵌套循環連接 Nested-Loop Join(NLJ) 算法 (走索引)
        一次一行循環地從第一張表(稱為驅動表)中讀取行,在這行數據中取到關聯字段,根據關聯字段在另一張表(被驅動表)里取出滿足條件的行,然后取出兩張表的結果合集。 
      • Block Nested-Loop Join 算法(不走索引)
        把驅動表的數據讀入到 join_buffer 中,然后掃描被驅動表,把被驅動表每一行取出來跟 join_buffer 中的數據做對比。
         
        對於關聯sql的優化
        關聯字段加索引:讓mysql做join操作時盡量選擇NLJ算法
        小表驅動大表:寫多表連接sql時如果明確知道哪張表是小表可以用straight_join寫法固定連接驅動方式,省去mysql優化器自己判斷的時間 

     3.2 in和exsits優化

 select * from A where id in (select id from B)

        in:當B表的數據集小於A表的數據集時,in優於exists 

        exists:當A表的數據集小於B表的數據集時,exists優於in 

      3.3 count(*)查詢優化

         字段有索引:count(*)≈count(1)>count(字段)>count(主鍵 id) //字段有索引,count(字段)統計走二級索引,二級索引存儲數據比主鍵索引少,所以count(字段)>count(主鍵 id)
        字段無索引:count(*)≈count(1)>count(主鍵 id)>count(字段) //字段沒有索引count(字段)統計走不了索引,count(主鍵 id)還可以走主鍵索引,所以count(主鍵 id)>count(字段) 
 

5、阿里Mysql規范解讀

  1、數值類型
  

優化建議
1. 如果整形數據沒有負數,如ID號,建議指定為UNSIGNED無符號類型,容量可以擴大一倍。
2. 建議使用TINYINT代替ENUM、BITENUM、SET。
3. 避免使用整數的顯示寬度(參看文檔最后),也就是說,不要用INT(10)類似的方法指定字段顯示寬度,直接用
INT。
4. DECIMAL最適合保存准確度要求高,而且用於計算的數據,比如價格。但是在使用DECIMAL類型的時候,注意
長度設置。
5. 建議使用整形類型來運算和存儲實數,方法是,實數乘以相應的倍數后再操作。
6. 整數通常是最佳的數據類型,因為它速度快,並且能使用AUTO_INCREMENT。
 

2、日期和時間 
   

  優化建議
  1. MySQL能存儲的最小時間粒度為秒。
  2. 建議用DATE數據類型來保存日期。MySQL中默認的日期格式是yyyy-mm-dd。
  3. 用MySQL的內建類型DATE、TIME、DATETIME來存儲時間,而不是使用字符串。
  4. 當數據格式為TIMESTAMP和DATETIME時,可以用CURRENT_TIMESTAMP作為默認(MySQL5.6以后),MySQL會自動返回記錄插入的確切時間。
  5. TIMESTAMP是UTC時間戳,與時區相關。
  6. DATETIME的存儲格式是一個YYYYMMDD HH:MM:SS的整數,與時區無關,你存了什么,讀出來就是什么。
  7. 除非有特殊需求,一般的公司建議使用TIMESTAMP,它比DATETIME更節約空間,但是像阿里這樣的公司一般會用DATETIME,因為不用考慮TIMESTAMP將來的時間上限問題。
  8. 有時人們把Unix的時間戳保存為整數值,但是這通常沒有任何好處,這種格式處理起來不太方便,我們並不推薦它。

 

3、字符串 
  

  優化建議

  1. 字符串的長度相差較大用VARCHAR;字符串短,且所有值都接近一個長度用CHAR。
  2. CHAR和VARCHAR適用於包括人名、郵政編碼、電話號碼和不超過255個字符長度的任意字母數字組合。那些要用來計算的數字不要用VARCHAR類型保存,因為可能會導致一些與計算相關的問題。換句話說,可能影響到計算的准確性和完整性。
  3. 盡量少用BLOB和TEXT,如果實在要用可以考慮將BLOB和TEXT字段單獨存一張表,用id關聯。
  4. BLOB系列存儲二進制字符串,與字符集無關。TEXT系列存儲非二進制字符串,與字符集相關。
  5. BLOB和TEXT都不能有默認值。 
 

五、事務

  1、事務的特點(ACID)

    1. 原子性:一個事務是對數據庫操作的最小單位,不可細分。
    2. 隔離性:事務之間可以同時執行,不會相互干擾。
    3. 一致性:事務執行成功數據庫變更,事務執行失敗則不會變更。
    4. 持久性:事務執行成功以后,結果是持久的。

  2、事務的隔離級別

    

 

    常看當前數據庫的事務隔離級別: show variables like 'tx_isolation';
    設置事務隔離級別:set tx_isolation='REPEATABLE-READ';
    Mysql默認的事務隔離級別是可重復讀,用Spring開發程序時,如果不設置隔離級別默認用Mysql設置的隔離級別,如果Spring設置了就用已經設置的隔離級別

   3、鎖

    結論1

    1、對MyISAM表的讀操作(加讀鎖) ,不會阻寒其他進程對同一表的讀請求,但會阻賽對同一表的寫請求。只有當讀鎖釋放后,才會執行其它進程的寫操作。
    2、對MylSAM表的寫操作(加寫鎖) ,會阻塞其他進程對同一表的讀和寫操作,只有當寫鎖釋放后,才會執行其它進程的讀寫操作
    3、MyISAM在執行查詢語句SELECT前,會自動給涉及的所有表加讀鎖,在執行update、insert、delete操作會自動給涉及的表加寫鎖。
    4、InnoDB在執行查詢語句SELECT時(非串行隔離級別),不會加鎖。但是update、insert、delete操作會加行鎖。

    鎖優化建議

    1、盡可能讓所有數據檢索都通過索引來完成,避免無索引行鎖升級為表鎖
    2、合理設計索引,盡量縮小鎖的范圍
    3、盡可能減少檢索條件范圍,避免間隙鎖
    4、盡量控制事務大小,減少鎖定資源量和時間長度,涉及事務加鎖的sql盡量放在事務最后執行
    5、盡可能低級別事務隔離



 

 


 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM