一、SQL性能下降的原因
- 查詢語句問題,各種連接、子查詢
- 索引失效(單值索引、復合索引)
- 服務器調優及各個參數設置(緩沖、線程池等)
二、索引 排好序的快速查找數據結構
1. 索引分類
- 單值索引 一個索引只包含單個列,一個表可以有多個
- 復合索引 一個索引包含多個列
- 唯一索引 索引的值必須唯一,但是允許空值
默認使用B+樹索引,除B+樹索引外,還有哈希索引(hash index)等
2. 索引結構
- B-Tree 索引
- Hash 索引
- Full-Text 索引
- R-Tree 索引
B-Tree 示意圖

3. 索引的優勢和劣勢
- 優勢 提高檢索效率、降低排序成本
- 劣勢 索引需要占用空間,降低表更新速度
4. 基本語法
創建
CREATE [UNIQUE] INDEX indexName ON mytable(column_list); ALTER mytable ADD [UNIQUE] INDEX indexName ON (column_list); ALTER mytable ADD PRIMARY KEY (column_list); ALTER mytable ADD FULLTEXT indexName(column_list);
刪除
DROP INDEX indexName ON mytable;
查看
SHOW INDEX FROM table_name\G;
三、explain執行計划
id select 查詢的序列號,表示查詢中執行select子句或操作表的順序
- id相同,執行順序由上至下
- id不同,如果是子查詢,id的序號會遞增,id值越大優先級越高
- id有相同有不同,id如果相同,可以認為是一組,從上往下順序執行;所有組中,id值越大,優先級越高,越先執行
select_type 查詢的類型,主要是用於區別普通查詢、聯合查詢、子查詢等復雜查詢
- SIMPLE 簡單的select查詢,查詢中不包含子查詢或union
- PRIMARY 查詢中若包含任何復雜的子部分,最外層查詢則被標記為primary
- SUBQUERY 在select或where列表中包含了子查詢
- DERIVED 在from列表中包含的子查詢被標記為DERIVED(衍生),MySQL會遞歸執行這些子查詢,把結果放在臨時表里
- UNION 若第二個select出現在UNION之后,則被標記為UNION
- UNION RESULT 從UNION表獲取結果的select
table 顯示這一行數據是關於哪張表的
type 訪問類型
- system 表只有一行記錄(等於系統表),這是const類型的特例,平時不會出現,這個可以忽略不計
- const 表示通過索引一次就找到了,用於比較primary key或unique索引
- eq_ref 唯一性索引掃描,對於每個索引,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描
- ref 非唯一性索引掃描,返回所有匹配某個單獨值的所有行
- range 只檢索給定范圍的行,使用一個索引來選擇行。key列顯示了使用哪個索引,一般是where語句中出現了between、<、>、in等
- index Full Index Scan,index與all的區別為index類型只遍歷索引樹。這通常比All快,因為索引文件通常比數據文件小。
- ALL 全表掃描
possible_keys 可能用到的索引
key 實際使用的索引。如果為NULL,則沒有使用索引。查詢中若使用了覆蓋索引,則該索引僅出現在Key列中。
key_len 表示索引中使用的字節數,顯示為索引字段的最大可能長度,並非實際使用長度。
ref 顯示索引的哪一列被使用了,如果可能的話,是一個常數。
rows 根據表統計信息及索引選用情況,大致估算出找到所需的記錄所要讀取的行數。
extra 包含不適合在其它列中顯示但是十分重要的額外信息。
- Using Filesoft MySQL無法利用索引完成的排序操作成為文件排序
- Using temporary 使用了臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序order by 和分組查詢group by。
- Using Index 表示相應的select操作中使用了覆蓋索引(Covering Index),避免訪問了表的數據,如果同時出現了Using Where 表名索引被用來執行索引鍵值的查找,如果沒有出現Using Where 表名索引用來讀取數據而非執行查找動作。
- Using where 表明使用了where過濾
- Using join buffer 使用了連接緩存, 出現在兩表連接時,驅動表沒有索引的情況下,給驅動表建立索引可解決此問題,type將變成ref
- Impossible where where子句的值總是false,不能用來獲取任何元組
※ 覆蓋索引
select的數據列只用從索引中就能夠取得,不必讀取數據行,MySQL可以利用索引返回select列表中的字段,而不必根據索引再次讀取數據文件,換句話說,查詢列要被所建的索引覆蓋。
四、一般查詢優化
- 盡量使用全值匹配
- 最左前綴法則,如果索引了多列,查詢從索引的最左列開始並且不跳過索引中的列。
- 不要在索引列上做任何操作(計算、函數、類型轉換)
- 存儲引擎不能使用索引中范圍條件右邊的列
- 盡量使用覆蓋索引
- 使用不等於無法使用索引
- is not null 無法使用索引,但是is null 可以的
- like以通配符開頭索引失效,以通配符結尾索引type為range ,通配符開頭和結尾用覆蓋索引type為index
- 字符串不加單引號索引失效(底層轉換、使用函數)
- 用or連接時,索引失效
- 多表查詢有索引的情況下inner join效果是最好的,如果用in或exists注意小表驅動大表
※ Order By 排序使用索引情況

五、查詢截取分析
1. 分析方案
- 開啟慢查詢日志,例如設置超過5秒就是慢SQL
- explain + 慢SQL分析
- show profile 查詢SQL在MySQL服務器的執行細節和生命周期情況
- SQL服務器參數調優
2. 慢查詢日志
使用方法
查看開啟 SHOW VARIABLES LIKE '%slow_query_log%';
開啟 SET GLOBAL slow_query_log=1; #針對當前數據庫有效
查看闕值時間 SHOW VARIABLES LIKE 'long_query_time%';
修改 SHOW VARIABLES LIKE 'long_query_time%'; #需要重新連接或新開一個會話才能看到修改值。
set session long_query_time=1 #改變當前session變量;
查詢當前系統中有多少條慢查詢記錄 SHOW GLOBAL STATUS LIKE '%Slow_queries%';
分析工具
mysqldumpslow

得到返回記錄集最多的10個SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
得到訪問次數最多的10個SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
得到按照時間排序的前10條里面含有左連接的查詢語句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
另外建議在使用這些命令時結合 | 和more 使用 ,否則有可能出現爆屏情況
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
3. Show Profile
開啟功能,默認是關閉,使用前需要開啟
show variables like 'profiling';
set profiling=1;
運行SQL
select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5;
查看結果
show profiles;
診斷SQL
show profile cpu,block io for query n
type:
| ALL --顯示所有的開銷信息
| BLOCK IO --顯示塊IO相關開銷
| CONTEXT SWITCHES --上下文切換相關開銷
| CPU --顯示CPU相關開銷信息
| IPC --顯示發送和接收相關開銷信息
| MEMORY --顯示內存相關開銷信息
| PAGE FAULTS --顯示頁面錯誤相關開銷信息
| SOURCE --顯示和Source_function,Source_file,Source_line相關的開銷信息
| SWAPS --顯示交換次數相關開銷的信息
日常開發需要注意的結論
- converting HEAP to MyISAM 查詢結果太大,內存都不夠用了往磁盤上搬了。
- Creating tmp table 創建臨時表
- Copying to tmp table on disk 把內存中臨時表復制到磁盤,危險!
- locked
5. 全局查詢日志
配置啟用
在mysql的my.cnf中,設置如下:
#開啟
general_log=1
# 記錄日志文件的路徑
general_log_file=/path/logfile
#輸出格式
log_output=FILE
編碼啟用
set global log_output='TABLE';
select * from mysql.general_log;
六、MySQL鎖機制
1. MyISAM鎖
MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行增刪改操作前,會自動給涉及的表加寫鎖。
MySQL的表級鎖有兩種模式:
表共享讀鎖(Table Read Lock)
表獨占寫鎖(Table Write Lock)
鎖類型 他人可讀 他人可寫
讀鎖 是 否
寫鎖 否 否
結論:
結合上表,所以對MyISAM表進行操作,會有以下情況:
1、對MyISAM表的讀操作(加讀鎖),不會阻塞其他進程對同一表的讀請求,但會阻塞對同一表的寫請求。只有當讀鎖釋放后,才會執行其它進程的寫操作。
2、對MyISAM表的寫操作(加寫鎖),會阻塞其他進程對同一表的讀和寫操作,只有當寫鎖釋放后,才會執行其它進程的讀寫操作。
簡而言之,就是讀鎖會阻塞寫,但是不會堵塞讀。而寫鎖則會把讀和寫都堵塞
2. Innodb鎖
InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是采用了行級鎖.
事務(Transaction)及其ACID屬性
事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個屬性,通常簡稱為事務的ACID屬性。
l 原子性(Atomicity):事務是一個原子操作單元,其對數據的修改,要么全都執行,要么全都不執行。
l 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。
l 隔離性(Isolation):數據庫系統提供一定的隔離機制,保證事務在不受外部並發操作影響的“獨立”環境執行。這意味着事務處理過程中的中間狀態對外部是不可見的,反之亦然。
l 持久性(Durable):事務完成之后,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。
並發事務處理帶來的問題
1>更新丟失(Lost Update)
當兩個或多個事務選擇同一行,然后基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最后的更新覆蓋了由其他事務所做的更新。
例如,兩個程序員修改同一java文件。每程序員獨立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋前一個程序員所做的更改。
如果在一個程序員完成並提交事務之前,另一個程序員不能訪問同一文件,則可避免此問題。
2>臟讀(Dirty Reads)
一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“臟”數據,並據此做進一步的處理,就會產生未提交的數據依賴關系。這種現象被形象地叫做”臟讀”。
一句話:事務A讀取到了事務B已修改但尚未提交的的數據,還在這個數據基礎上做了操作。此時,如果B事務回滾,A讀取
的數據無效,不符合一致性要求。
3>不可重復讀(Non-Repeatable Reads)
在一個事務內,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問該同一數據。那么,在第一個事務的兩次讀數據之間。由於第二個事務的修改,那么第一個事務讀到的數據可能不一樣,這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為不可重復讀,即原始讀取不可重復。
一句話:一個事務范圍內兩個相同的查詢卻返回了不同數據。
4>幻讀(Phantom Reads)
一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為“幻讀”。
一句話:事務A 讀取到了事務B提交的新增數據,不符合隔離性。
事務隔離級別
臟讀”、“不可重復讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。

數據庫的事務隔離越嚴格,並發副作用越小,但付出的代價也就越大,因為事務隔離實質上就是使事務在一定程度上 “串行化”進行,這顯然與“並發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重復讀”和“幻讀”並不敏感,可能更關心數據並發訪問的能力。
常看當前數據庫的事務隔離級別:show variables like 'tx_isolation';
Innodb存儲引擎由於實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會要更高一些,但是在整體並發處理能力方面要遠遠優於MyISAM的表級鎖定的。當系統並發量較高的時候,Innodb的整體性能和MyISAM相比就會有比較明顯的優勢了。
但是,Innodb的行級鎖定同樣也有其脆弱的一面,當我們使用不當的時候,可能會讓Innodb的整體性能表現不僅不能比MyISAM高,甚至可能會更差。
- 盡可能讓所有數據檢索都通過索引來完成,避免無索引行鎖升級為表鎖。
- 盡可能較少檢索條件,避免間隙鎖
- 盡量控制事務大小,減少鎖定資源量和時間長度
- 鎖住某行后,盡量不要去調別的行或表,趕緊處理被鎖住的行然后釋放掉鎖。
- 涉及相同表的事務,對於調用表的順序盡量保持一致。
- 在業務環境允許的情況下,盡可能低級別事務隔離
