高性能MySQL讀書筆記

高性能MySQLmysql

第一章 MySQL架構

MySQL最重要、不同凡響的特性時它的存儲引擎架構,這種架構的設計將查詢處理及其餘系統任務和數據存儲、提取相分離。
這種處理和存儲的設計能夠在使用時根據性能、特性以及其餘需求來選擇數據的存儲方式

image.png

mysql服務器邏輯架構圖算法

  • 最上層並非MySQL獨有的,大多數基於網絡的客戶端/服務器的工具或者服務都有相似的架構,如鏈接處理、受權認證、安全等
  • 第二層、查詢解析、分析、優化、緩存以及全部內置函數。全部誇存儲引擎的功能都在這一層實現:存儲過程,觸發器,視圖
  • 第三層包含了存儲引擎

1.2 併發控制

  • 服務器層
  • 存儲引擎層sql

    1.2.1 讀寫鎖

    • 共享鎖/排他鎖 或者 讀鎖/寫鎖

    1.2.2 鎖粒度

    所謂鎖策略,就是在鎖的開銷和數據的安全性之間尋求平衡。數據庫

    • 表鎖

    是鎖最基本的鎖策略,而且開銷最小的策略。
    服務器會爲alter table之類的語句使用表鎖,而忽略存儲引擎的鎖機制設計模式

    • 行級鎖

    最大程度的支持併發處理,同時也帶來了最大程度的鎖開銷。api

    1.3 事務

    一組原子性的sql查詢,或者說一個獨立的工做單元。緩存

    • A
    • C
    • I
    • D

    1.3.1 隔離級別

    • Read Uncommitted
    • Read Committed
    • Repeatable Read

    可重複讀隔離級別仍是沒法解決另一個幻讀的問題:當某一個事務在讀取某個範圍內的記錄時,另外一個事務由在該範圍內插入了新的記錄,當以前的事務再次讀取該範圍的記錄時,會產生幻行安全

    • Serializable

1.3.2 死鎖

1.3.3 事務日誌

使用事務日誌,存儲引擎在修改表的數據時只須要修改其內存拷貝,再把該修改行爲記錄持久化在硬盤上事務日誌中,而不用每次都將修改的數據自己持久到磁盤。事務日誌採用的是追加的方式,所以寫日誌的操做是磁盤上一小塊區域的順序I/O,
Wtite ahead Logging性能優化

1.3.4 Mysql中的事務

Mysql默認採用autocommit模式服務器

SET TRANSACTION ISOLATION LEVEL  
設置隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
設置數據庫的隔離級別
  • 隱式鎖和顯式鎖

Innodb採用的是兩階段鎖定協議,在事務執行過程當中,隨時均可以執行鎖定,鎖只有在執行commit或者rollback的時候纔會釋放。而且全部鎖在同一時間釋放

  • 顯式鎖

    SELECT ... LOCK IN SHARE MODE
    SELECT ... FOR UPDATE

## 1.4 多版本併發控制
MVCC的實現,是經過保存數據在某個時間點的快照來實現的。也就是說,無論須要執行多長時間,每一個事務看到的數據都是一致的。根據事務開始的時間不一樣,每一個事務對同一張表,同一時刻看到的數據可能不同。

REPEATABLE READ隔離級別下MVCC實現

SELECT 
 a.InnoDB查找版本早於當前事務版本的數據行,這樣能夠確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或修改的。
 b.行的刪除版本要麼爲定義,要麼大於當前事務版本號。這能夠確保事務讀取到的行,在事務開始以前未被刪除。
INSERT
    Innodb 爲新插入的每一行保存當前系統版本號做爲行版本號
DELETE
    InnoDB 爲刪除的每一行保存當前系統版本號做爲刪除標識
UPDATE
    InnoDB爲插入一行新記錄,保存當前系統版本號做爲行版本號,同時保存當前系統版本號到原來的行做爲行刪除標識

第三章 服務器性能你剖析

第四章 Shcema與數據類型優化

  • 邏輯設計 物理設計和查詢執行

4.1 選擇優化的數據類型

  • 避免null

null對於mysql來講更難優化,由於可爲null的列使得索引、索引統計信息和值都更復雜

DateTime 和Timestamp均可以存儲相同類型的數據,時間和日期,精確到秒。而Timestamp只使用DateTime通常的存儲空間,而且會根據時區變化,具備自動更新的能力。

4.1.1 整數類型

存儲空間(bit)
TINYINT 8
SMALLINT 16
MEDIUMINT 24
INT 32
BIGINT 64

4.1.2 實數類型

儘可能值在對小數進行精確計算時才使用decimal

4.1.3 字符串類型

varchar 和 char

  • VARCHAR
    用於存儲可變長的字符出串,比定長類型更節省空間,。若是使用ROW_FORMAT=FIXED的話,每一行都會使用定長存儲。

VARCHAR須要使用1或2個額外字節記錄字符串長度。
可是因爲時變長,在update時可能使得行變得比原來長,這就致使須要額外的工做。 若是一個行佔用的空間增加,而且頁內沒有更多空間存儲時,不一樣存儲引擎處理方式不一樣。
MyISAM會將行拆成不一樣的片斷存儲。 InnoDB須要分裂頁來使得行可存儲。

  • CHAR
    mysql根據定義的字符串長度分配足夠的空間,當存儲char值時,mysql會刪除全部末尾空格。
使用枚舉代替字符串類型

mysql在內會將每一個值在列表中的位置保存爲證書,並在.frm文件中保存數字-字符串的映射關係
枚舉字段時按照內部存儲的證書而不是定義的字符串進行排序的。

4.2 MYSQL schema設計中的陷阱

  • 太多的列
  • 太多的關聯
「實體-屬性-值」設計模式是一個常見的糟糕的設計模式,尤爲是在MySQL下不能靠譜的工做。 mysql限制了每一個關聯操做最多隻能有61張表
  • 全能的枚舉
  • 變相的枚舉

4.3 範式和反範式

在範式化的數據庫中,每一個事實數據都會出現而且只出現一次。相反在反範式化的數據庫中,信息時冗餘的,可能會存儲在多個地方。

4.3.1 範式的優勢和缺點

  • 範式的更新操做一般比反範式快
  • 數據比較好的範式化時,就只有不多或者沒有重複數據,因此只須要修改更少的數據
  • 範式化的表一般更小,能夠更好的存放在內存裏,因此執行操做會更快
  • 不多由多餘的數據意味着檢索列表數據須要更少的distinct或者group by
  • 範式化的schema缺點是須要關聯,

4.3.2 反範式的優勢和缺點

  • 能夠很好的避免關聯
  • 若是不須要關聯,對大部分查詢最差的狀況,即便沒有使用索引,是全表掃描。當數據比內存大時,這可能比關聯的要快得多,避免了隨機i/o

4.5 加快alter table操做的速度

mysql執行大部分修改表結構操做的方法時用新的結構建立一個空表,從舊錶中查出全部數據插入新表,而後刪除舊錶

  • 修改.frm文件

列的默認值實際上存放在.frm文件中,因此能夠直接修改這個文件而不須要改動表自己。

alter colum 操做改變列的默認值,而不涉及改表的數據。

4.6 總結

  • 儘可能避免過分設計
  • 使用小而簡單的合適數據類型
  • 儘可能使用象通的數據類型存儲類似或相關的值
  • 注意可變長字符串,其在臨時表和排序是可能致使悲觀的按最大長度分配內存
  • 使用整形定義標識列
  • 當心使用enum和set

第五章 建立高性能的索引

5.1 索引基礎

5.1.1 索引的類型

在mysql中,索引實在存儲引擎而不是在服務器層實現的

B-TREE

B-TREE一般意味着全部的值都是按順序存儲,而且每個葉子頁到根的距離象通
image.png
B-TREE索引

B-TREE對索引列的順序組織存儲的,很適合查找範圍數據

可使用b-tree索引的查詢類型:全鍵值、鍵值範圍或鍵前綴查找

  • 全值匹配
  • 匹配最左前綴
  • 匹配列前綴
  • 匹配範圍值
  • 精確匹配某一列並範圍匹配另一列
  • 只訪問索引的查詢(索引覆蓋)

關於B-tree索引的限制

  • 若是不是按照索引的最左列開始查找,則沒法使用索引
  • 不能跳過索引中的列
  • 若是查詢某個列的範圍查詢,其右邊全部的列都無發使用索引優化
哈希索引

mysql中只有memory引擎顯式支持哈希索引

哈希索引的限制

  • 哈希索引只包含哈希值和行指針,而不存儲字段值,因此不能使用索引中的值來避免讀取行
  • 哈希索引不能用於排序
  • 沒法用域範圍查找
  • 只支持等值比較

Innodb引擎有一個特殊功能叫作自適應哈希:當Innodb注意到某些索引值被使用的很是頻繁時,它會在內存中基於B-Tree索引值上再建立一個哈希索引。這樣就讓B-tree索引也具備哈希索引的一些優勢

空間數據索引
全文索引

5.2 索引的優勢

  • 大大減小了服務器須要掃描的數據量
  • 因此能夠幫助服務器避免排序和臨時表
  • 索引能夠將隨機I/O變爲順序I/O
relational  database index design and the optimizers
---by Tapio lahdenmak and Mike leach
索引並不老是最好的工具
- 對於很是小的表,大部分狀況下簡單的全表掃描更有效
- 對於中到大型的表,索引就很是有效
- 對於特大型的表,創建和使用索引的代價將隨之增加,可使用分區技術

5.3 高性能的索引策略

5.3.1 獨立的列

若是不是獨立的列, mysql就不會使用索引,獨立的列時指索引列不能是表達式的一部分

5.3.2 前綴索引的索引選擇性

索引選擇性(cardinality) 是指不重複的索引值和數據表總記錄的比值

select count(*) as cnt LEFT(city,7) as pref from sakil.city_demo group by pref order by cnt desc limit 10;

經過統計發現前綴長度到達7的時候,再增長前綴長度選擇性提高的幅度已經很小了。
建立前綴索引
alter table sakila.city_demo add key(city(7));

5.3.3 多列索引

  • 當出現服務器對多個索引作香蕉操做時,意味着須要一個包含全部相關列的多列索引
  • 當服務器須要對多個索引作聯合操做時,一般須要耗費大量cpu和內存資源,再算法的緩存、排序和合並上
  • 優化器不會把這些計算到查詢成本上,優化器只關心隨機頁面讀取,這會使得查詢成本被低估。

5.3.4 選擇合適的索引列順序

索引從最左列進行匹配:
選擇性最高的列放到索引最前列

可是性能不僅是依賴於全部索引列的選擇性,也和查詢條件和具體值有關,也就是和值的分佈有關。

5.3.5 聚簇索引

聚簇索引並非一種單獨的索引類型,而時一種數據存儲方法。具體的細節依賴於其實現方式,但InnoDB的聚簇索引實際上再同一個結構中保存了B-TREE索引和數據行

當由聚簇索引時,它的數據行實際上存放在索引的葉子頁中。 聚簇表示數據行和相鄰的鍵值緊湊的存儲在一塊兒。由於沒法同時把數據行存放在兩個不一樣的地方,因此一個表只能有一個聚簇索引。
Innodb過主鍵彙集數據。 若是沒有定義主鍵,InnoDB會選擇一個惟一的非空索引代替,若是沒有這樣的索引,InnoDB會隱式定義一個主鍵來做爲聚簇索引。
InnoDB只彙集再同一個頁面中的記錄,包含相鄰鍵值的頁面可能會相距甚遠。

聚簇索引的一些優勢

  • 能夠把相關數據保存在一塊兒,下降i/o次數
  • 數據訪問更快
  • 使用覆蓋索引掃描的查詢能夠直接使用頁節點中的主鍵值

聚簇索引的缺點

  • 極大提升了i/o密集型應用的性能,可是若是數據所有都放在內存中,則訪問順序就沒那麼重要了,聚簇索引也就沒有優點了
  • 插入速度嚴重依賴於插入順序,按照逐漸的順序插入時加載數據到InnoDB表中速度最快的方式,可是若是不是按照主鍵順序加載數據,那麼再加載完成後最好使用optimize table命令從新組織如下表
  • 更新聚簇索引列代價很高,由於會強制InnoDB將每一個被更新的行移動到新的位置
  • 基於聚簇作因的表插入新航,或者主鍵被更新致使須要移動時,可能面臨頁分裂的問題
  • 聚簇索引可能致使全表掃描變慢,尤爲是比較稀疏,或者因爲頁分裂致使數據存儲不連續的時候
  • 耳機索引可能比想一想的更大,由於在二級索引的葉子節點包含了引用行的主鍵列
  • 二級索引訪問須要兩次索引查找
InnoDB和myisam的數據分佈對比
  • MyISAM的數據分佈很是簡單,按照數據插入的順序存儲在磁盤上。

myIsam中主鍵和其餘索引在結構上並無什麼不一樣
image.png
myisam索引結構圖

  • InnoDB

聚簇索引就是表,因此不想myISAM那樣須要獨立的行存儲
聚簇索引的每個葉子節點都包含了主鍵值、事務Id,用於事務和MVCC的回滾指針以及全部剩餘列
innodb的二級索引的葉子節點中存儲的不是行指針,而時主鍵值,並以此做爲指向行的指針。這樣的策略減小了出現行移動時或者數據頁分裂時二級索引的維護工做
image.png

Innodb表的主鍵分佈
image.png

聚簇索引和非聚簇表對比

在InnoDB表中主鍵順序插入行
  • AUTO_INCREMENT

最好避免隨機的聚簇索引

隨機寫入的缺點

  • 寫入的目標也已經刷到磁盤上並從緩存中移除,或者時尚未被加載到緩存中,InnoDB在插入以前不得不先找到並從磁盤中讀取目標頁到內存中,這將致使大量的隨機I/O
  • 由於寫入時亂序的,InnoDB不得不頻繁的作頁分裂
  • 因爲頻繁的頁分裂,頁會變得稀疏而且不被規則的填充,因此最終會有碎片

隨機值載入聚簇索引以後,也須要作一次OPTIMIZE_TABLE 來重建表並優化頁的填充

5.3.6 覆蓋索引

若是一個索引包含全部查詢字段的值,咱們就稱之爲覆蓋索引

覆蓋索引帶來的好處

  • 索引條目一般遠小於數據行大小
  • 索引是按照列值順序存儲的
  • 一些存儲引擎,如myISAM在內存中只緩存索引,數據則依賴於操做系統來緩存,所以要訪問數據須要一次系統調用,這可能會致使嚴重的性能問題,尤爲那些系統調用佔用了數據訪問中最大開銷大的場景
  • INNODB中 若是二級主鍵可以覆蓋查詢,則能夠避免對主鍵索引的二次查詢。
當發起一個被索引覆蓋的查詢時,在EXPLAIN的extra列能夠看到using index 的信息。

索引沒法發起覆蓋查詢的緣由

  • 沒有任何索引可以覆蓋這個查詢
  • mysql不能再索引中執行like操做

5.3.7 使用索引掃描來排序

MYSQL 有郎中方式能夠生成有序結果

  • 經過排序操做
  • 按索引順序掃描

若是explain 出來的type列值爲index 則說明mysql使用了索引掃描來排序

掃描索引自己時很快的,由於只須要從一條索引記錄移動到緊接着的下一條記錄。但若是索引不能覆蓋查詢所需的所有列,那就不得不沒掃描一條索引記錄就都回表查詢一次對應的行。這基本上都是隨機I/O,所以按索引順序讀取數據的速度一般要比順序的全表掃描慢,尤爲時I/O密集性的工做負載
只有當索引列的順序和order by子句順序徹底一致時,而且全部列排序方向同樣時, mysql才能使用索引對結果作排序。
如通查詢須要關聯多張表,則只有當order by 子句引用的字段所有是第一個表示,才能使用索引作排序。

order by 子句和查找型查詢的限制是同樣的,須要知足索引最左前綴的要求,不然,mysql都須要執行排序操做,而不發使用索引排序

有一種狀況order by 子句能夠不知足索引的最左前綴的要求,就是前導列爲常量的時候,

5.3.11 索引和鎖

InnoDB 只有在訪問行的時候纔會對其加鎖,而索引可以減小InnoDB訪問的次數,從而減小鎖的數量。 但這隻有當InnoDB在存儲引擎層可以過濾掉全部不須要的行時纔有效。

5.6 總結

選擇索引和編寫利用這些索引查詢時

  • 單行訪問時很慢的,,若是服務器從存儲中讀取一個數據塊只是爲了獲取其中一行,那麼就浪費了不少工做,最好讀取塊中能儘量包含所須要的行
  • 按順序訪問方位數據是很快的, 第一 順序I/O不須要屢次磁盤尋道,因此比隨機I/O要快不少。第二,若是服務器可以按須要順序讀取數據,就不須要額外的排序工做
  • 索引覆蓋擦汗尋時很快的

第六章 查詢性能優化

查詢優化、庫表優化、索引優化

查詢的生命週期大體能夠按照順序來看:從客戶端、服務器、而後在服務器上進行解析、生成執行計劃、執行、返回結果給客戶端。
其中執行能夠認爲是整個生命週期中最重要的階段,這其中包括愛了大量爲檢索數據到存儲引擎的調用以及調用後的數據處理、包括排序、分組。

在完成這些任務的時候查詢須要在不一樣地方花費時間,包括網絡、CPU計算、生成統計信息和執行計劃、鎖等待等操做。尤爲是像底層存儲引擎檢索數據的調用操做。這些調用須要在內存操做、cpu操做和內存不足時致使的I/O操做上消耗時間,根據存儲引擎不一樣,可能還會產生大量的上下文切換以及系統調用。

6.2 慢查詢基礎:優化數據訪問

一、確認應用程序是否在檢索大量超過須要的數據
二、確認mySQL服務器是否在分析大量查過須要的數據行

6.2.1 請求了不須要的數據

  • 查詢不須要的記錄
    在查詢後加上limit
  • 多表關聯時返回所有列
  • 老是取出所有列
  • 查詢相同數據(加緩存)

6.2.2 mysql是否在掃描額外記錄

  • 響應時間
    服務時間和排隊時間
  • 掃描行數
  • 返回行數
    理想狀況下掃描的行數和返回的行數應該是相同的 可是在作關聯查詢時,服務器必須掃描多行才能生成結果集中的幾行,掃描的行數對放回的行數比例通常在1:1到10:1之間
  • 掃描的行數和訪問類型
訪問類型:
全表掃描
索引所秒
範圍掃描
惟一索引掃描
常數引用

通常mysql可以使用以下三種方式應用where條件

  • 在索引中使用where條件來過慮不匹配的記錄,這是在存儲引擎層完成的
  • 使用索引覆蓋來返回記錄,直接從索引中過濾不須要的記錄並返回命中結果,這是在mysql服務層完成的無需再回表查詢
  • 從數據表中返回數據,而後過濾不知足條件的記錄(Using where)這在mysql服務器層完成

6.3 重構查詢的方式

6.3.1 一個複雜的查詢仍是多個簡單查詢

6.3.2 切分查詢

delete from messages where created < DATE.SUB(now(),INTERVASL 3 MONTH);

 rows_affected = 0
 do {
     row_affected = do_query(
         "delete from messages where created < DATE_SUB(NOW(),INTERVAL 3 MONTH ) limit 1000")
} while rows_affected > 0

6.3.3 分解關聯查詢

優點

  • 讓緩存的效率更高。 對於mysql的查詢緩存來講秒若是關聯中某個表發生了變化,那麼就沒法使用查詢緩存了。而差分以後,若是某個表不多改變,基於該表的查詢就能夠重複利用
  • 查詢分解後,執行單個查詢能夠減小鎖的競爭
  • 在應用層關聯,能夠更容易對數據庫有進行拆分,更容易作到高性能和擴展性
  • 能夠減小榮譽記錄的查詢

6.4 查詢執行的基礎

image.png
查詢執行路徑

  • 客戶端發送一條查詢給服務器
  • 服務器先檢查查詢緩存
  • 服務端進行sql解析、預處理、再由優化器生成對應的執行計劃
  • mysql根據優化器生成的執行計劃,調用春初引擎的api來執行查詢
  • 將結果返回給客戶端

6.4.1 MySQL客戶端/服務器通訊協議

半雙工通訊協議,意味着在任什麼時候刻,要麼是服務器想客戶端發送數據,要麼是由客戶端向服務器發送數據,這兩個動做不能同時發生。
也意味着,沒法進行流量控制。一旦一段開始發送消息,另外一端要接受完整消息才能響應它。

查詢狀態

SHOW FULL PROCESSLIST
查看當mysql的狀態

  • sleep
    線程正在等待客戶端發送新請求
  • query
    線程正在執行查詢或者正在將結果發送給客戶端
  • locked
    再mysql服務器層,該線程正在等待表鎖,在存儲引擎級別實現的鎖,例如InnoDB的行鎖是不會體如今線程狀態中。
  • analyzing and statistics
    線程正在收集存儲引擎的統計信息,並生成查詢執行計劃
  • copying to tmp table
    線程正在執行查詢,而且將其結果都複製到一個臨時表中,這個狀態通常是在作GROUP BY操做、要麼是文件排序操做或者時UNION操做
  • sorting result
    線程正在對結果進行排序
  • sending data

6.4.2 查詢緩存

6.4.3 查詢優化處理

這個階段包括:解析SQL,預處理,優化SQL執行計劃

語法解析器和預處理
查詢優化器

mysql可以處理的優化類型

  • 從新定義關聯表的順序
  • 將外鏈接轉化成內鏈接
  • 使用等價變換規則
  • 優化count、min、max
  • 預估並轉化爲常數表達式
  • 覆蓋索引掃描
  • 子查詢優化
  • 提早終止查詢(limit)
  • 等值傳播
  • 列表in()
    in()徹底等同於多個or條件子句。mysql將IN()列表的數據進行排序,而後經過二分查找的方式來肯定列表中的值是否知足條件。這是一個O(logn)複雜度的操做

等價轉化成or語句複雜度爲O(N)

數據和索引的統計信息
mySQL如何執行關聯查詢

嵌套循環關聯

UNION爲例, mySQL首先將一系列的單個查詢結果放到一個臨時表中,而後從新讀出臨時表的數據完成union操做

當前mysql關聯執行的策略很簡單,mysql對任何關聯執行嵌套循環關聯操做,即mysql先在一個表中循環讀出單條數據,而後再嵌套循環到下一個表中尋找匹配的行。依次下去。

全外鏈接就沒法經過嵌套循環和回溯的方式完成,這是當發線關聯表中沒有找到任何匹配行的時候,則多是由於關聯時剛好從一個沒有任何匹配的表開始

執行計劃

mysql執行計劃老是一個左側深度優先的樹

排序優化

排序是一個成本很高的左槽,從性能角度考慮,應該儘量避免排序或者儘量避免對大量數據進行排序。
當不能經過索引進行排序時,mysql須要本身進行排序,若是數據量小則再內存中進行,若是數據量大則須要使用磁盤,不過mysql將這個過程統一稱爲文件排序(file sort)

6.4.4 查詢執行引擎

6.4.5 返回結果給客戶端

mysql將結果返回給客戶端是一個增量、逐步返回的過程

6.5 mysql查詢優化器的侷限性

6.5.1 關聯子查詢

IN的查詢
例子

select * from sakila.file where film_id in ( select file_id from sakila.film_actor where actor_id = 1);

實際上mysql會將象關的外城表壓到子查詢,它認爲這樣能夠更高效率地查找到數據行

select * from sakila.file where exists(select * from sakila.film_actor where actor_id = 1 and film_actor.film_id = film.film_id);

能夠改寫這個查詢爲

select film.* from sakila.file INNER JOIN sakila.film_actor USING(film_id) where actor_id = 1;

使用IN()加子查詢性能可能回不好,因此一般建議使用exists()等效改寫查詢得到更好的效率

6.5.5 並行查詢

mysql沒法利用多核特性來並行執行查詢。儘管其餘不少關係型數據庫可以提供這個特性

6.5.9 再同一個而表上查詢和更新

mysql不遜於對同一張表同時進行查詢和更新

6.7 優化特定類型地查詢

6.7.1 優化count

  • 統計列值的數量(非null)
  • 統計行數

一般count()須要掃描大量的數據
「快速、精確和實現簡單」三者永遠只能知足其二

6.7.2 優化關聯查詢

  • 確保on 或者using子句中的列上索引。再建立索引的額時候急須要考慮到關聯的順序。
  • 確保任何group by 和 order by 中的表達式只涉及到一個表中的列,這樣mysql纔有可能使用索引優化這個過程
相關文章
相關標籤/搜索