1. 程式人生 > >MySQL深入研究--學習總結(3)

MySQL深入研究--學習總結(3)

#前言 接上文,繼續學習後續章節。細心的同學已經發現,我整理的並不一定是作者講的內容,更多是結合自己的理解,加以闡述,所以建議結合原文一起理解。 # 第九章《普通索引和唯一索引,如何選擇》 ## 從查詢和更新效率上看 通過唯一索引查詢時:找到對應主鍵索引,就停止檢索,返回資料。 通過普通索引查詢時:找到第一個符合要求的記錄後,還要繼續往下查詢,直至找到第一個不滿足條件的記錄。 從流程上看,似乎通過普通索引查詢的效率比唯一索引低,當實際上這個差距微乎其微的,因為MySQL是以資料頁為讀取單位的。找到記錄後,將記錄所在的整個資料頁都讀到記憶體,找記憶體中查詢的效率是很快的,兩者的差距可以忽略不計的。 描述更新流程之前先跟你介紹下插入快取change buffer(insert buffer): ![](https://img2020.cnblogs.com/blog/874710/202103/874710-20210305114727857-1226976329.png) change buffer 使用的緩衝池的記憶體。 當有資料要更新時,如果這個資料就在記憶體中,那麼直接修改記憶體即可。如果不在記憶體中,則把更新的操作記錄在change buffer中,當再來查詢這個條更新的資料時,查詢在記憶體的時候便可直接返回,不在記憶體的資料,需要先將資料頁讀取到記憶體,並依據change buffer的資料合併修正為最新的資料返回。 顯然如果只更新記憶體,當MySQL宕機,資料是沒有保障的,下面我們結合redo log、undo log,緩衝區,change buffer,表空間,梳理下當有資料要更新時,都做了哪些事? 1、當我們根據非唯一索引更新時,會先開啟事務,然後查詢記憶體中該條資料所在的資料頁是否在記憶體中。 2、若存在時,直接更新記憶體,並同時將該條資料的最新結果記入在redo log中處於prepared狀態,將更新前的資料狀態記入在undo log中。 3、當undo log儲存成功後,將redo log中狀態改為commit狀態,事務提交後,清除undo log,至此更新結束,返回結果。 4、若更新的資料不在記憶體中,那麼就直接記入在change buffer中,後面的流程就是一樣的。 這裡只是大致的描述了下,一個更新流程,實際上還會涉及bin log,redo log buffer等,後面會繼續更深入的梳理。 ## 索引的選擇 通過上面講解可知由於唯一索引用不上change buffer的優化機制,因此如果業務可以接受,從效能角度出發我建議你優先考慮非唯一索引。當然,業務優先。 # 第十章《MySQL為什麼有時候會選錯索引》 我們知道索引的選擇取決於優化器選擇索引。 而優化器選擇索引的目的,是找到一個最優的執行方案,並採用最小的代價去執行語句。比如掃描行數,掃描行數越少意味著訪問磁碟的次數越少,消耗CPU資源越少。除此之外,是否使用臨時表,是否排序等因素也是優化器的衡量標準之一。 若優化器選錯了索引,大多情況下,是因為統計預計掃描行數出錯導致的,文中模擬了一個導致掃描出錯的場景,向我們展示了,選錯索引導致的查詢耗時變大的情況。 知道了問題出在哪,那針對的方法有: 1、通過analyze table 表名,命令來修正統計資訊。 2、通過force index 強行指定使用哪個索引. 3、刪除不必要的索引,或者通過SQL語句引導優化器選擇正確的索引。 其實這章闡述的問題意義不在於,我們如何解決索引選錯的問題,主要大家得理解其本質,本身這類問題發生的概率極其小,即使發生了我們也有排查的思路。 #第十一章《怎麼給字串欄位加索引?》 當你需要給一個長字串加索引時,怎麼如何處理?比如郵箱,身份證等。 首先當我們給一個字串加索引時,應該考量的是改欄位的區分度高不高,比如郵箱這個欄位,我們可以發現郵箱的後面幾位@XX.COM大部分是一致的市面上常用郵箱可能也就十幾種。如果我們給整個郵箱欄位加索引,那麼索引說佔的空間就打,一頁上儲存的索引數量也就小。 所以我們以考慮建立字首索引: ```sql mysql> alter table SUser add index index2(email(6)); ``` 但具體擷取幾位,還是得再庫裡通過distinct驗證下區分度.選擇合適的位數。 那麼使用字首索引會有哪些問題? 當我們使用覆蓋索引的時候,通過email欄位檢索,就不需要回表查詢,但是使用字首索引,郵箱欄位不全,還需要回表查詢一次,便多了一次查詢。 所以字首索引不適合可以覆蓋索引的場景。 那麼當身份證這樣的欄位,如何建索引? 我們可以發現身份證前6位很多地區都是一致的,索引如果使用字首索引,顯然不合適,然後一般後面的幾位區分度是很大的,所以我們可以考慮,儲存的時候,倒序儲存再建立字首索引。這樣索引佔用的空間就大大減少了。 還有一種方案就是新建一個欄位,儲存身份證的hash值,通過crc32函式計算。但是hash之後還是有可能不一樣的身份證算出來的結果一樣,但是這個概念很小很小, 所以查詢需要再驗證身份證是否一樣。 其實通過上面的例子,可以總結出,我們建立索引的最終目的,一是儘量讓區分度大的欄位,提升查詢效率,二在不影響查詢效率的情況下,儘量索引欄位小一點。這個可以是以後我們將索引時總要的衡量標準。 總結: 1 . 直接建立完整索引,這樣可能比較佔用空間; 2 . 建立字首索引,節省空間,但會增加查詢掃描次數,並且不能使用覆蓋索引; 3 . 倒序儲存,再建立字首索引,用於繞過字串本身字首的區分度不夠的問題; 4 . 建立hash欄位索引,查詢效能穩定,有額外的儲存和計算消耗,跟第三種方式一樣,都不支援範圍掃描。 # 第十二章《為什麼我的MySQL會“抖”一下》 通過之前的學習我們知道,當你要插入或更新一條資料時,MySQ並不一定會立即寫入到表資料中,一般會做兩個操作,一更新緩衝池中對應的資料,二記錄在redo log中。然後會選擇合適的時機,再寫入到磁碟。 這樣實際表中的資料和緩衝池中的資料是不一致的,這就所謂的髒頁。 所以當MySQL需要重新整理髒頁的時候,就可能出現我們所謂的“抖”了一下。 那麼MySQL什麼時候會觸發刷髒頁呢? 1、當redo log日誌滿的時候,這時候為了能繼續記錄,就需要把redo log中清理部分資料,這個清理的前提就需要把不一致的資料重新整理到磁碟。這個時候由於redo log已滿,就不能再寫了,需要停止一些更新操作,等待清理,這是很嚴重的,所以MySQL一般會盡量避免redo log被寫滿的情況。 2、記憶體滿了,當需要讀取的資料不再記憶體中是,就需要從磁碟中讀取資料頁並載入到記憶體中,這時候發現記憶體已經滿了,就需要騰出記憶體,淘汰一些資料頁(最久不使用的),如果是髒頁就需要,刷磁碟。當然實際中,我們便不會等記憶體滿了才會刷髒頁。 3、當mysql覺得系統空閒了,也會主動重新整理髒頁。 4、當MySQL要退出時,也會把髒頁重新整理到磁碟。 ## InnoDB刷髒頁的控制策略 InnoDB的刷盤速度就是要參考這兩個因素:一個是髒頁比例,一個是redo log寫盤速度。 引數innoDB_max_dirty_pages_pct是髒頁上限比例,預設是75% InnoDB每次寫入的日誌都有一個序號,當前寫入的序號跟checkpoin t對應的序號之間的差值,我們假設為N 。InnoDB會根據這個N 算出一個範圍在0到100之間的數字,這個計算公式可以記為F2 ( N ) 。F2 ( N ) 演算法比較複雜,你只要知道N 越大,算出來的值越大就好了。 然後,根據上述算得的 根據上述算得的 F1(M) F1(M) 和F2(N) F2(N) 兩個值,取其中較大的值記為 兩個值,取其較大的值記為 R,之後引擎就可以按 ,之後引擎就可以按照innodb_io_capacit y innodb_io_capacit y定義的能力乘以 定義的能力乘以 R%來控制刷髒頁的速度。 ![](https://img2020.cnblogs.com/blog/874710/202103/874710-20210305114656795-1124997