影響mysql的性能因素
-
業務需求對MySQL的影響(合適合度)
-
存儲定位對MySQL的影響
- 不適合放進MySQL的數據
- 二進制多媒體數據
- 流水隊列數據
- 超大文本數據
- 需要放進緩存的數據
- 系統各種配置及規則數據
- 活躍用戶的基本信息數據
- 活躍用戶的個性化定制信息數據
- 准實時的統計信息數據
- 其他一些訪問頻繁但變更較少的數據
- 不適合放進MySQL的數據
-
Schema設計對系統的性能影響
- 盡量減少對數據庫訪問的請求
- 盡量減少無用數據的查詢請求
-
硬件環境對系統性能的影響
MySQL常見瓶頸
-
CPU:CPU在飽和的時候一般發生在數據裝入內存或從磁盤上讀取數據時候
-
IO:磁盤I/O瓶頸發生在裝入數據遠大於內存容量的時候
-
服務器硬件的性能瓶頸:top,free,iostat 和 vmstat來查看系統的性能狀態
性能下降SQL慢 執行時間長 等待時間長 原因分析
- 查詢語句寫的爛
- 索引失效(單值、復合)
- 關聯查詢太多join(設計缺陷或不得已的需求)
- 服務器調優及各個參數設置(緩沖、線程數等)
MySQL常見性能分析手段
在優化MySQL時,通常需要對數據庫進行分析,常見的分析手段有慢查詢日志,EXPLAIN 分析查詢,profiling分析以及show命令查詢系統狀態及系統變量,通過定位分析性能的瓶頸,才能更好的優化數據庫系統的性能。
性能瓶頸定位
我們可以通過 show 命令查看 MySQL 狀態及變量,找到系統的瓶頸:
Mysql> show status ——顯示狀態信息(擴展show status like ‘XXX’)
Mysql> show variables ——顯示系統變量(擴展show variables like ‘XXX’)
Mysql> show innodb status ——顯示InnoDB存儲引擎的狀態
Mysql> show processlist ——查看當前SQL執行,包括執行狀態、是否鎖表等
Shell> mysqladmin variables -u username -p password——顯示系統變量
Shell> mysqladmin extended-status -u username -p password——顯示狀態信息
Explain(執行計划)
是什么:使用 Explain 關鍵字可以模擬優化器執行SQL查詢語句,從而知道 MySQL 是如何處理你的 SQL 語句的。分析你的查詢語句或是表結構的性能瓶頸
各字段解釋
-
id(select 查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序)
- id相同,執行順序從上往下
- id全不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
- id部分相同,執行順序是先按照數字大的先執行,然后數字相同的按照從上往下的順序執行
-
select_type(查詢類型,用於區別普通查詢、聯合查詢、子查詢等復雜查詢)
- SIMPLE :簡單的select查詢,查詢中不包含子查詢或UNION
- PRIMARY:查詢中若包含任何復雜的子部分,最外層查詢被標記為PRIMARY
- SUBQUERY:在select或where列表中包含了子查詢
- DERIVED:在from列表中包含的子查詢被標記為DERIVED,MySQL會遞歸執行這些子查詢,把結果放在臨時表里
- UNION:若第二個select出現在UNION之后,則被標記為UNION,若UNION包含在from子句的子查詢中,外層select將被標記為DERIVED
- UNION RESULT:從UNION表獲取結果的select
-
table(顯示這一行的數據是關於哪張表的)
-
type(顯示查詢使用了那種類型,從最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )
- system:表只有一行記錄(等於系統表),是 const 類型的特例,平時不會出現
- const:表示通過索引一次就找到了,const 用於比較 primary key 或 unique 索引,因為只要匹配一行數據,所以很快,如將主鍵置於 where 列表中,mysql 就能將該查詢轉換為一個常量
- eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描
- ref:非唯一性索引掃描,范圍匹配某個單獨值得所有行。本質上也是一種索引訪問,他返回所有匹配某個單獨值的行,然而,它可能也會找到多個符合條件的行,多以他應該屬於查找和掃描的混合體
- range:只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引,一般就是在你的where語句中出現了between、<、>、in等的查詢,這種范圍掃描索引比全表掃描要好,因為它只需開始於索引的某一點,而結束於另一點,不用掃描全部索引
- index:Full Index Scan,index於ALL區別為index類型只遍歷索引樹。通常比ALL快,因為索引文件通常比數據文件小。(也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)
- ALL:Full Table Scan,將遍歷全表找到匹配的行
tip: 一般來說,得保證查詢至少達到range級別,最好到達ref
-
possible_keys(顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段若存在索引,則該索引將被列出,但不一定被查詢實際使用)
-
key
-
實際使用的索引,如果為NULL,則沒有使用索引
-
查詢中若使用了覆蓋索引,則該索引和查詢的 select 字段重疊,僅出現在key列表中
-

-
key_len
- 表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
- key_len顯示的值為索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的
-
ref (顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值)
-
rows (根據表統計信息及索引選用情況,大致估算找到所需的記錄所需要讀取的行數)
-
Extra(包含不適合在其他列中顯示但十分重要的額外信息)
-
using filesort: 說明mysql會對數據使用一個外部的索引排序,不是按照表內的索引順序進行讀取。mysql中無法利用索引完成的排序操作稱為“文件排序”。常見於order by和group by語句中
-
Using temporary:使用了臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
-
using index:表示相應的select操作中使用了覆蓋索引,避免訪問了表的數據行,效率不錯,如果同時出現using where,表明索引被用來執行索引鍵值的查找;否則索引被用來讀取數據而非執行查找操作
-
using where:使用了where過濾
-
using join buffer:使用了連接緩存
-
impossible where:where子句的值總是false,不能用來獲取任何元祖
-
select tables optimized away:在沒有group by子句的情況下,基於索引優化操作或對於MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段再進行計算,查詢執行計划生成的階段即完成優化
-
distinct:優化distinct操作,在找到第一匹配的元祖后即停止找同樣值的動作
-
case:

-
第一行(執行順序4):id列為1,表示是union里的第一個select,select_type列的primary表示該查詢為外層查詢,table列被標記為,表示查詢結果來自一個衍生表,其中derived3中3代表該查詢衍生自第三個select查詢,即id為3的select。【select d1.name......】
-
第二行(執行順序2):id為3,是整個查詢中第三個select的一部分。因查詢包含在from中,所以為derived。【select id,name from t1 where other_column=''】
-
第三行(執行順序3):select列表中的子查詢select_type為subquery,為整個查詢中的第二個select。【select id from t3】
-
第四行(執行順序1):select_type為union,說明第四個select是union里的第二個select,最先執行【select name,id from t2】
-
第五行(執行順序5):代表從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行union操作。【兩個結果union操作】
性能優化
索引優化
- 全值匹配我最愛
- 最佳左前綴法則,比如建立了一個聯合索引(a,b,c),那么其實我們可利用的索引就有(a), (a,b), (a,b,c)
- 不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
- 存儲引擎不能使用索引中范圍條件右邊的列
- 盡量使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減少select
- is null ,is not null 也無法使用索引
- like "xxxx%" 是可以用到索引的,like "%xxxx" 則不行(like "%xxx%" 同理)。like以通配符開頭('%abc...')索引失效會變成全表掃描的操作,
- 字符串不加單引號索引失效
- 少用or,用它來連接時會索引失效
- <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 則不行,會導致全表掃描
一般性建議
-
對於單鍵索引,盡量選擇針對當前query過濾性更好的索引
-
在選擇組合索引的時候,當前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。
-
在選擇組合索引的時候,盡量選擇可以能夠包含當前query中的where字句中更多字段的索引
-
盡可能通過分析統計信息和調整query的寫法來達到選擇合適索引的目的
-
少用Hint強制索引
查詢優化
永遠小標驅動大表(小的數據集驅動大的數據集)
slect * from A where id in (select id from B)`等價於
#等價於
select id from B
select * from A where A.id=B.id
復制代碼
當 B 表的數據集必須小於 A 表的數據集時,用 in 優於 exists
select * from A where exists (select 1 from B where B.id=A.id)
#等價於
select * from A
select * from B where B.id = A.id`
復制代碼
當 A 表的數據集小於B表的數據集時,用 exists優於用 in
注意:A表與B表的ID字段應建立索引。
order by關鍵字優化
-
order by子句,盡量使用 Index 方式排序,避免使用 FileSort 方式排序
-
MySQL 支持兩種方式的排序,FileSort 和 Index,Index效率高,它指 MySQL 掃描索引本身完成排序,FileSort 效率較低;
-
ORDER BY 滿足兩種情況,會使用Index方式排序;①ORDER BY語句使用索引最左前列 ②使用where子句與ORDER BY子句條件列組合滿足索引最左前列
-
盡可能在索引列上完成排序操作,遵照索引建的最佳最前綴
-
如果不在索引列上,filesort 有兩種算法,mysql就要啟動雙路排序和單路排序
- 雙路排序:MySQL 4.1之前是使用雙路排序,字面意思就是兩次掃描磁盤,最終得到數據
- 單路排序:從磁盤讀取查詢需要的所有列,按照order by 列在 buffer對它們進行排序,然后掃描排序后的列表進行輸出,效率高於雙路排序
-
優化策略
- 增大sort_buffer_size參數的設置
- 增大max_lencth_for_sort_data參數的設置
GROUP BY關鍵字優化
- group by實質是先排序后進行分組,遵照索引建的最佳左前綴
- 當無法使用索引列,增大
max_length_for_sort_data
參數的設置,增大sort_buffer_size
參數的設置 - where高於having,能寫在where限定的條件就不要去having限定了
數據類型優化
MySQL 支持的數據類型非常多,選擇正確的數據類型對於獲取高性能至關重要。不管存儲哪種類型的數據,下面幾個簡單的原則都有助於做出更好的選擇。
-
更小的通常更好:一般情況下,應該盡量使用可以正確存儲數據的最小數據類型。
簡單就好:簡單的數據類型通常需要更少的CPU周期。例如,整數比字符操作代價更低,因為字符集和校對規則(排序規則)使字符比較比整型比較復雜。
-
盡量避免NULL:通常情況下最好指定列為NOT NULL
分區、分表、分庫
MySQL分區
一般情況下我們創建的表對應一組存儲文件,使用MyISAM
存儲引擎時是一個.MYI
和.MYD
文件,使用Innodb
存儲引擎時是一個.ibd
和.frm
(表結構)文件。
當數據量較大時(一般千萬條記錄級別以上),MySQL的性能就會開始下降,這時我們就需要將數據分散到多組存儲文件,保證其單個文件的執行效率
能干嘛
- 邏輯數據分割
- 提高單一的寫和讀應用速度
- 提高分區范圍讀查詢的速度
- 分割數據能夠有多個不同的物理文件路徑
- 高效的保存歷史數據
怎么玩
首先查看當前數據庫是否支持分區
-
MySQL5.6以及之前版本:
SHOW VARIABLES LIKE '%partition%'; 復制代碼
-
MySQL5.6:
show plugins; 復制代碼
分區類型及操作
-
RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區。mysql將會根據指定的拆分策略,,把數據放在不同的表文件上。相當於在文件上,被拆成了小塊.但是,對外給客戶的感覺還是一張表,透明的。
按照 range 來分,就是每個庫一段連續的數據,這個一般是按比如時間范圍來的,比如交易表啊,銷售表啊等,可以根據年月來存放數據。可能會產生熱點問題,大量的流量都打在最新的數據上了。
range 來分,好處在於說,擴容的時候很簡單。
-
LIST分區:類似於按RANGE分區,每個分區必須明確定義。它們的主要區別在於,LIST分區中每個分區的定義和選擇是基於某列的值從屬於一個值列表集中的一個值,而RANGE分區是從屬於一個連續區間值的集合。
-
HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。
hash 分發,好處在於說,可以平均分配每個庫的數據量和請求壓力;壞處在於說擴容起來比較麻煩,會有一個數據遷移的過程,之前的數據需要重新計算 hash 值重新分配到不同的庫或表
-
KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值。
看上去分區表很帥氣,為什么大部分互聯網還是更多的選擇自己分庫分表來水平擴展咧?
- 分區表,分區鍵設計不太靈活,如果不走分區鍵,很容易出現全表鎖
- 一旦數據並發量上來,如果在分區表實施關聯,就是一個災難
- 自己分庫分表,自己掌控業務場景與訪問模式,可控。分區表,研發寫了一個sql,都不確定mysql是怎么玩的,不太可控
隨着業務的發展,業務越來越復雜,應用的模塊越來越多,總的數據量很大,高並發讀寫操作均超過單個數據庫服務器的處理能力怎么辦?
這個時候就出現了數據分片,數據分片指按照某個維度將存放在單一數據庫中的數據分散地存放至多個數據庫或表中。數據分片的有效手段就是對關系型數據庫進行分庫和分表。
區別於分區的是,分區一般都是放在單機里的,用的比較多的是時間范圍分區,方便歸檔。只不過分庫分表需要代碼實現,分區則是mysql內部實現。分庫分表和分區並不沖突,可以結合使用。
說說分庫與分表的設計
MySQL分表
分表有兩種分割方式,一種垂直拆分,另一種水平拆分。
-
垂直拆分
垂直分表,通常是按照業務功能的使用頻次,把主要的、熱門的字段放在一起做為主要表。然后把不常用的,按照各自的業務屬性進行聚集,拆分到不同的次要表中;主要表和次要表的關系一般都是一對一的。
-
水平拆分(數據分片)
單表的容量不超過500W,否則建議水平拆分。是把一個表復制成同樣表結構的不同表,然后把數據按照一定的規則划分,分別存儲到這些表中,從而保證單表的容量不會太大,提升性能;當然這些結構一樣的表,可以放在一個或多個數據庫中。
水平分割的幾種方法:
- 使用MD5哈希,做法是對UID進行md5加密,然后取前幾位(我們這里取前兩位),然后就可以將不同的UID哈希到不同的用戶表(user_xx)中了。
- 還可根據時間放入不同的表,比如:article_201601,article_201602。
- 按熱度拆分,高點擊率的詞條生成各自的一張表,低熱度的詞條都放在一張大表里,待低熱度的詞條達到一定的貼數后,再把低熱度的表單獨拆分成一張表。
- 根據ID的值放入對應的表,第一個表user_0000,第二個100萬的用戶數據放在第二 個表user_0001中,隨用戶增加,直接添加用戶表就行了。

MySQL分庫
為什么要分庫?
數據庫集群環境后都是多台 slave,基本滿足了讀取操作; 但是寫入或者說大數據、頻繁的寫入操作對master性能影響就比較大,這個時候,單庫並不能解決大規模並發寫入的問題,所以就會考慮分庫。
分庫是什么?
一個庫里表太多了,導致了海量數據,系統性能下降,把原本存儲於一個庫的表拆分存儲到多個庫上, 通常是將表按照功能模塊、關系密切程度划分出來,部署到不同庫上。
優點:
-
減少增量數據寫入時的鎖對查詢的影響
-
由於單表數量下降,常見的查詢操作由於減少了需要掃描的記錄,使得單表單次查詢所需的檢索行數變少,減少了磁盤IO,時延變短
但是它無法解決單表數據量太大的問題
分庫分表后的難題
分布式事務的問題,數據的完整性和一致性問題。
數據操作維度問題:用戶、交易、訂單各個不同的維度,用戶查詢維度、產品數據分析維度的不同對比分析角度。 跨庫聯合查詢的問題,可能需要兩次查詢 跨節點的count、order by、group by以及聚合函數問題,可能需要分別在各個節點上得到結果后在應用程序端進行合並 額外的數據管理負擔,如:訪問數據表的導航定位 額外的數據運算壓力,如:需要在多個節點執行,然后再合並計算程序編碼開發難度提升,沒有太好的框架解決,更多依賴業務看如何分,如何合,是個難題。
主從復制
復制的基本原理
-
slave 會從 master 讀取 binlog 來進行數據同步
-
三個步驟
- master將改變記錄到二進制日志(binary log)。這些記錄過程叫做二進制日志事件,binary log events;
- salve 將 master 的 binary log events 拷貝到它的中繼日志(relay log);
- slave 重做中繼日志中的事件,將改變應用到自己的數據庫中。MySQL 復制是異步且是串行化的。
復制的基本原則
- 每個 slave只有一個 master
- 每個 salve只能有一個唯一的服務器 ID
- 每個master可以有多個salve
復制的最大問題
- 延時
三個范式
- 第一范式(1NF):數據庫表中的字段都是單一屬性的,不可再分。這個單一屬性由基本類型構成,包括整型、實數、字符型、邏輯型、日期型等。
- 第二范式(2NF):數據庫表中不存在非關鍵字段對任一候選關鍵字段的部分函數依賴(部分函數依賴指的是存在組合關鍵字中的某些字段決定非關鍵字段的情況),也即所有非關鍵字段都完全依賴於任意一組候選關鍵字。
- 第三范式(3NF):在第二范式的基礎上,數據表中如果不存在非關鍵字段對任一候選關鍵字段的傳遞函數依賴則符合第三范式。所謂傳遞函數依賴,指的是如 果存在"A → B → C"的決定關系,則C傳遞函數依賴於A。因此,滿足第三范式的數據庫表應該不存在如下依賴關系: 關鍵字段 → 非關鍵字段 x → 非關鍵字段y
百萬級別或以上的數據如何刪除
關於索引:由於索引需要額外的維護成本,因為索引文件是單獨存在的文件,所以當我們對數據的增加,修改,刪除,都會產生額外的對索引文件的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執行效率。所以,在我們刪除數據庫百萬級別數據的時候,查詢MySQL官方手冊得知刪除數據的速度和創建的索引數量是成正比的。
- 所以我們想要刪除百萬數據的時候可以先刪除索引(此時大概耗時三分多鍾)
- 然后刪除其中無用數據(此過程需要不到兩分鍾)
- 刪除完成后重新創建索引(此時數據較少了)創建索引也非常快,約十分鍾左右。
- 與之前的直接刪除絕對是要快速很多,更別說萬一刪除中斷,一切刪除會回滾。那更是坑了。