MySQL 原理篇
當客戶端向 MySQL 發送一個請求的時候,MySQL 的執行過程如下圖所示:
MySQL 客戶端/服務端通信
通信機制
MySQL 客戶端與服務端的通信方式是 “ 半雙工 ”。
- 全雙工:雙向通信,發送同時也可以接收
- 半雙工:雙向通信,同時只能接收或者是發送,無法同時做操作
- 單工:只能單一方向傳送
一旦一端開始發送消息,另一端要接收完整個消息才能響應它,所以我們無法也無須將一個消息切成小塊獨立發送,也沒有辦法進行流量控制。
客戶端用一個單獨的數據包將查詢請求發送給服務器,所以當查詢語句很長的時候,需要設置 max_allowed_packet 參數。
但是需要注意的是,如果查詢實在是太大,服務端會拒絕接收更多數據並拋出異常。
與之相反的是,服務器響應給用戶的數據通常會很多,由多個數據包組成。但是當服務器響應客戶端請求時,客戶端必須完整的接收整個返回結果,而不能簡單的只取前面幾條結果,然後讓服務器停止發送。
因而在實際開發中,儘量保持查詢簡單且只返回必需的數據,減小通信間數據包的大小和數量是一個非常好的習慣,這也是查詢中儘量避免使用 SELECT * 以及加上 LIMIT 限制的原因之一。
連接狀態
對於一個 MySQL 的連接,或者說一個線程,時刻都有一個狀態來標識這個連接正在做什麼。
可以通過如下命令來查看連接的狀態:
show full processlist show processlist
詳細的狀態集描述參考官網:https://dev.mysql.com/doc/refman/5.7/en/general-thread-states.html
這裏簡單介紹幾個常用的連接狀態:
- Sleep:線程正在等待客戶端發送數據
- Query:連接線程正在執行查詢
- Locked:線程正在等待表鎖的釋放
- Sorting result:線程正在對結果進行排序
- Sending data:向請求端返回數據
對於出現問題的連接可以通過 kill {id} 的方式進行殺掉。
查詢緩存
工作原理:
- 緩存 SELECT 操作的結果集和 SQL 語句。
- 新的 SELECT 語句,先去查詢緩存,判斷是否存在可用的記錄集,需要注意的是在判斷的時候,要求 SQL 語句完全一樣(SQL 兩端允許存在空格)纔會匹配到緩存數據。
緩存參數
MySQL 的緩存參數在配置文件中設置,可以通過如下命令來查看緩存的參數:
show variables like 'query_cache%'
- query_cache_type
- 0:不啓用查詢緩存,默認值
- 1:啓用查詢緩存,只要符合查詢緩存的要求,客戶端的查詢語句和記錄集都可以緩存起來,供其他客戶端使用,SQL 語句中加上 SQL_NO_CACHE 將不緩存
- 2:啓用查詢緩存,只要查詢語句中添加了參數:SQL_CACHE,且符合查詢緩存的要求,客戶端的查詢語句和記錄集,則可以緩存起來,供其他客戶端使用
- query_cache_size
- 總的緩存池的大小,允許設置 query_cache_size 的值最小爲40K,默認1M,推薦設置爲64M/128M
- 當總的緩存池大小超過設置的值時,會按照時間順序,讓最老的緩存失效
- query_cache_limit
- 指定單個查詢能夠使用的緩衝區大小,默認設置爲1M
緩存執行情況
可以通過如下命令來查看緩存情況:
show status like 'Qcache%'
- Qcache_free_blocks
- Query Cache 中目前還有多少剩餘的 blocks。如果該值顯示較大,則說明 Query Cache 中的內存碎片較多了,可能需要尋找合適的機會進行整理
- Qcache_free_memory
- Query Cache 中目前剩餘的內存大小。通過這個參數我們可以較爲準確的觀察出當前系統中的Query Cache 內存大小是否足夠,是需要增加還是過多了
- Qcache_hits
- 緩存命中次數。通過這個參數我們可以查看到 Query Cache 的基本效果
- Qcache_inserts
- 插入緩存的記錄數,通過 Qcache_hits 和 Qcache_inserts 兩個參數我們就可以算出 Query Cache 的命中率,Query Cache 命中率 = Qcache_hits / ( Qcache_hits + Qcache_inserts )
- Qcache_lowmem_prunes
- 多少條 Query 因爲內存不足而被清除出 Query Cache。通過 Qcache_lowmem_prunes 和 Qcache_free_memory 相互結合,能夠更清楚的瞭解到我們系統中 Query Cache 的內存大小是否真的足夠,是否經常出現因爲內存不足而有 Query 被清除
- Qcache_not_cached
- 因爲 query_cache_type 的設置或者不能被 cache 的 Query 的數量
- Qcache_queries_in_cache
- 當前 Query Cache 中 cache 的 Query 數量
- Qcache_total_blocks
- 當前 Query Cache 中的 block 數量
不會緩存的情況
- 當查詢語句中設置了 SQL_NO_CACHE,則不會被緩存。
- 當查詢語句中有一些不確定的數據時,則不會被緩存。如包含函數 NOW() ,CURRENT_DATE() 等類似的函數,或者用戶自定義的函數,存儲函數,用戶變量等都不會被緩存。
- 當查詢的結果大於 query_cache_limit 設置的值時,結果不會被緩存。
- 對於 InnoDB 引擎來說,當一個語句在事務中修改了某個表,那麼在這個事務提交之前,所有與這個表相關的查詢都無法被緩存。因此長時間執行事務,會大大降低緩存命中率。
- 查詢的表是系統表。
- 查詢語句不涉及到表。
緩存有哪些坑?
- 在查詢之前必須先檢查是否命中緩存,浪費計算資源。
- 如果這個查詢可以被緩存,那麼執行完成後,MySQL 發現查詢緩存中沒有這個查詢,則會將結果存入查詢緩存,這會帶來額外的系統消耗。
- 針對表進行寫入或更新數據時,將對應表的所有緩存都設置失效。
- 如果查詢緩存很大或者碎片很多時,這個操作可能帶來很大的系統消耗。
適用場景
以讀爲主的業務,數據生成 之後就不常改變的業務,比如門戶類、新聞類、報表類、論壇類。
查詢優化處理
查詢優化處理的三個階段
- 解析 SQL
- 通過 lex 語法分析,yacc 語法分析將 SQL 語句解析成解析樹。
- lex、yacc 語法參考:https://www.ibm.com/developerworks/cn/linux/sdk/lex/
- 預處理階段
- 根據 MySQL 的語法的規則進一步檢查解析樹的合法性,如:檢查數據的表和列是否存在,解析名字和別名的設置。還會進行權限的驗證
- 查詢優化器
- 優化器的主要作用就是找到最優的執行計劃
查詢優化器如何找到最優執行計劃
這裏介紹幾種優化方式,更多的可以參考《高性能MySQL_第3版(中文)》
- 使用等價變化規則
- 5 = 5 and a > 5 改寫成 a > 5
- a < b and a = 5 改寫成 b > 5 and a = 5
- 基於聯合索引,調整條件位置等
- 優化 count、min、max 等函數
- InnoDB 引擎 min 函數只需找索引最左邊
- InnoDB 引擎 max 函數只需找索引最右邊
- MyISAM 引擎 count(*),不需要計算,直接返回
- 覆蓋索引掃描
- 子查詢優化
select * from (select * from user where id = 1) as t;
,會被優化成一級查詢
- 提前終止查詢
- 用了 limit 關鍵字或者使用不存在的條件,獲取到 limit 所需要的數據後,就不再遍歷接下來的數據
- IN 的優化
- MySQL 對於 IN 的查詢,會先進性排序,再採用二分查找的方式查找數據
- 比如表中的數據是 1,2,3,4,5,where 條件是 id IN(2,1,3),在進行 IN 操作的時候,會先對 IN 中的數據排序,變成 1,2,3,然後取出一條數據1先和2比較,1<2,則往2的左邊查找,進而找到1,接下來就是再獲取一條數據重複上面的查找步驟。
- 其他關係型數據庫不會採用二分查找的方式,而是和 or 的方式一樣,where id=1 or id=2 or id=3,從表中獲取一條數據和 where 條件中的 or 的數據一個一個比對。
MySQL 的查詢優化器是基於成本計算的原則,它會嘗試各種執行計劃,數據抽樣的方式進行試驗(隨機的讀取一個 4K 的數據塊進行分析)。
執行計劃
這塊內容比較多,後面會單獨提供一篇文章描述
查詢執行引擎
調用插件式的存儲引擎的原子 API 進行執行計劃的執行。
返回客戶端
- 有需要做緩存的,執行緩存操作
- 增量的返回執行結果,開始生成第一條結果時,MySQL 就開始往請求方逐步返回數據,這樣做的好處是 MySQL 服務器無須保存過多的數據,浪費內存,用戶體驗好,馬上就拿到了數據