實時監控語句耗時與SQL狀態
使用語句show full processlist; 此命令有權限,自己的賬號只能看自己的連接,root賬號可以看全部連接。
字段 值 說明
Id 整數 連接和會話的唯一ID
User 字符串 哪個用戶使用了此連接或者會話
Host 字符串 由那個ip的客戶端連接到服務的
db 字符串 數據庫名稱
Command 字符串 連接執行命令狀態,例如:一般就是休眠(sleep),查詢(query),連接(connect)
Time 整數 空閑或者執行語句時間,如果是空閑,這個時間代表空閑了多久。如果是執行,那就是這個sql執行所經過的時間(sql執行的太久屬於不正常現象)
State 字符串 詳細見下方state字段描述
Info 字符串 如果是執行就顯示正在執行的sql語句。因為長度有限,所以長的sql語句就顯示不全,但是一個判斷問題語句的重要依據。
state字段描述
顯示使用當前連接的sql語句的狀態,很重要的列,可用來判斷mysql的運行狀態。 這個命令中最關鍵的就是state列,mysql列出的狀態主要有以下幾種:
Checking table
正在檢查數據表(這是自動的)。
Closing tables
正在將表中修改的數據刷新到磁盤中,同時正在關閉已經用完的表。這是一個很快的操作,如果不是這樣的話,就應該確認磁盤空間是否已經滿了或者磁盤是否正處於重負中。
Connect Out
復制從服務器正在連接主服務器。
Copying to tmp table on disk
由於臨時結果集大於tmp_table_size,正在將臨時表從內存存儲轉為磁盤存儲以此節省內存。
Creating tmp table
正在創建臨時表以存放部分查詢結果。
deleting from main table
服務器正在執行多表刪除中的第一部分,剛刪除第一個表。
deleting from reference tables
服務器正在執行多表刪除中的第二部分,正在刪除其他表的記錄。
Flushing tables
正在執行FLUSH TABLES,等待其他線程關閉數據表。
Killed
發送了一個kill請求給某線程,那么這個線程將會檢查kill標志位,同時會放棄下一個kill請求。MySQL會在每次的主循環中檢查kill標志位,不過有些情況下該線程可能會過一小段才能死掉。如果該線程程被其他線程鎖住了,那么kill請求會在鎖釋放時馬上生效。
Locked
被其他查詢鎖住了。
Sending data
正在處理Select查詢的記錄,同時正在把結果發送給客戶端。
Sorting for group
正在為GROUP BY做排序。
Sorting for order
正在為ORDER BY做排序。
Opening tables
這個過程應該會很快,除非受到其他因素的干擾。例如,在執Alter TABLE或LOCK TABLE語句行完以前,數據表無法被其他線程打開。正嘗試打開一個表。
Removing duplicates
正在執行一個Select DISTINCT方式的查詢,但是MySQL無法在前一個階段優化掉那些重復的記錄。因此,MySQL需要再次去掉重復的記錄,然后再把結果發送給客戶端。
Reopen table
獲得了對一個表的鎖,但是必須在表結構修改之后才能獲得這個鎖。已經釋放鎖,關閉數據表,正嘗試重新打開數據表。
Repair by sorting
修復指令正在排序以創建索引。
Repair with keycache
修復指令正在利用索引緩存一個一個地創建新索引。它會比Repair by sorting慢些。
Searching rows for update
正在講符合條件的記錄找出來以備更新。它必須在Update要修改相關的記錄之前就完成了。
Sleeping
正在等待客戶端發送新請求.
System lock
正在等待取得一個外部的系統鎖。如果當前沒有運行多個mysqld服務器同時請求同一個表,那么可以通過增加--skip-external-locking參數來禁止外部系統鎖。
Upgrading lock Insert DELAYED
正在嘗試取得一個鎖表以插入新記錄。
Updating
正在搜索匹配的記錄,並且修改它們。
User Lock
正在等待GET_LOCK()。
Waiting for tables
該線程得到通知,數據表結構已經被修改了,需要重新打開數據表以取得新的結構。然后,為了能的重新打開數據表,必須等到所有其他線程關閉這個表。以下幾種情況下會產生這個通知:FLUSH TABLES tbl_name, Alter TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE,或OPTIMIZE TABLE。 waiting for handler insert
Insert DELAYED
已經處理完了所有待處理的插入操作,正在等待新的請求。 大部分狀態對應很快的操作,只要有一個線程保持同一個狀態好幾秒鍾,那么可能是有問題發生了,需要檢查一下。
殺死連接
語法為kill id; 例:kill 62532 使用方式是使用show full processlist 人工判斷出鎖表的會話,使用kill殺死此會話。不只是鎖表的需要殺死,可能某些建立索引的,或者特別耗時的sql也需要殺死,原則就是無法很快處理掉的會話都需要殺死,如果不殺死可能會嚴重拖慢mysql的效率而造成死機,更多的慢sql等等,
記錄導出慢SQL日志
通過
查看是否開啟了慢sql日志以及日志位置。 配置my.ini文件(Linux下文件名為my.cnf), 查找到[mysqld]區段,增加日志的配置,如下示例:
[mysqld]
slow_query_log = 1; #開啟
slow_query_log_file=/var/lib/mysql/slow_query.log #慢日志地址,缺省文件名host_name-slow.log
long_query_time=5; #運行時間超過該值的SQL會被記錄,默認值>10,單位s(秒)
log_output=FILE
Linux下這些配置項應該已經存在,只是被注釋掉了,可以去掉注釋。 未使用索引的查詢被記錄到慢查詢日志中。如果調優的話,建議開啟這個選項。如果開啟了這個參數,full index scan的sql也會被記錄到慢查詢日志中。
show variables like '%log_queries_not_using_indexes%'
set global log_queries_not_using_indexes=1
分析慢查詢日志
mysqldumpslow 慢日志分析工具。 命令: -s 按照那種方式排序 c:訪問計數 l:鎖定時間 r:返回記錄 al:平均鎖定時間 ar:平均訪問記錄數 at:平均查詢時間 -t 是top n的意思,返回多少條數據。 -g 可以跟上正則匹配模式,大小寫不敏感。 得到返回記錄最多的20個sql:
mysqldumpslow -s r -t 20 sqlslow.log
得到平均訪問次數最多的20條sql:
mysqldumpslow -s ar -t 20 sqlslow.log
得到平均訪問次數最多,並且里面含有ttt字符的20條sql:
mysqldumpslow -s ar -t 20 -g "ttt" sqldlow.log
注: 如果出現如下錯誤,Died at /usr/bin/mysqldumpslow line 161, <> chunk 405659.說明你要分析的sql日志太大了,請拆分后再分析 拆分的命令為:
tail -100000 mysql-slow.log>mysql-slow.20180725.log
pt-query-digest,為另外一款慢sql分析工具,也推薦使用。 Mysqlsla,功能最全面的慢sql分析工具。
執行計划與慢SQL分析
Explain分析 Explain是Mysql的自帶查詢優化器,負責select語句的優化器模塊,可以模擬優化器執行SQL查詢語句,從而知道Mysql是如何處理SQL的,語法也很簡單:Explain + SQL。 以下是通過explain查詢出的幾個屬性
(常見性能瓶頸 —— CPU:CPU飽和一般發生在數據裝入內存或從磁盤上讀取數據時 IO:磁盤I/O瓶頸發生在裝入數據遠大於內存容量時 服務器硬件的性能瓶頸:top,free,iostat,vmstat來查看系統的性能狀態) 用途: (1)表的讀取順序,id (2)數據讀取操作的操作類型,select_type (3)哪些索引可以使用 (4)哪些索引被實際使用 (5)表之間的引用 (6)每張表有多少行被優化器查詢 rows
id:反映的是表的讀取的順序,或查詢中執行select子句的順序。 小表永遠驅動大表,三種情況: (1)id相同,執行順序是由上至下的 (2)id不同,如果是子查詢,id序號會遞增,id值越大優先級越高,越先被執行 (3)id存在相同的,也存在不同的,所有組中,id越大越先執行,如果id相同的,從上往下順序執行
derived是衍生虛表的意思,derived2中的2對應id2
select_type:反映的是Mysql理解的查詢類型 (1)simple:簡單的select查詢,查詢中不包含子查詢或union。 (2)primary:查詢中若包含任何復雜的字部分,最外層查詢標記為primary。 (3)subquery:select或where列表中的子查詢。 (4)derived(衍生):在from列表中包含的子查詢,Mysql會遞歸執行這些子查詢,把結果放在臨時表里。 (5)union:若第二個select出現在union后,則被標記為union,若union包含在from字句的子查詢中,外層select將被標記為derived (6)union result:union后的結果集
table:反映這一行數據是關於哪張表的
type:訪問類型排序反映sql優化的狀態,至少達到range級別,最好能達到ref查詢效率:system > const > eq_ref > ref > range > index > all (完整的排序:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >all) (1)system:從單表只查出一行記錄(等於系統表),這是const類型的特例,一般不會出現 (2)const:查詢條件用到了常量,通過索引一次就找到,常在使用primary key或unique索引中出現。
where id=1寫死,所以類型是const (3)eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描。 (4)ref:非唯一性索引掃描,返回匹配某個單獨值的所有行,本質上也是一種索引訪問,它可能會找到多個符合條件的行,與eq_ref的差別是eq_ref只匹配了一條記錄。 (5)range:只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引,一般是在where語句中出現了between、<、>、in等的查詢。這種范圍掃描索引掃描比全表掃描要好,因為它只需要開始於索引的某一點,而結束於另一點,不用掃描全部索引。與eq_ref和ref的區別在於篩選條件不是固定值,是范圍。
(6)index:full Index scan,index和all的區別為index類型只遍歷索引樹。這通常比all快,因為索引文件通常比數據文件小。
要獲得的id信息,剛好id在索引上,從索引中讀取(all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的) (7)all:全表掃描,如果查詢數據量很大時,全表掃描效率是很低的。
possible_keys、key、key_len:反映實際用到了哪個索引,索引是否失效 (1)possible_keys:Mysql推測可能用到的索引有哪些,但不一定被查詢實際使用 (2)key:實際使用的索引,若為null,則可能沒建索引或索引失效。
(查詢中若使用了覆蓋索引,則該索引僅出現在key列表中。 覆蓋索引:select后面的字段和所建索引的個數、順序一致) (3)key_len:表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。同樣的查詢結果下,長度越短越好。 key_len顯示的值為索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的。
ref:反映哪些列或常量被用於查找索引列上的值
rows:根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數
僅通過主鍵索引查找是641行
-
建完相關的復合索引再查,需要查詢的行數就變少了
-
Extra (1)using filesort:mysql中無法利用索引完成的排序,這時會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。
創建索引時就會對數據先進行排序,出現using filesort一般是因為order by后的條件導致索引失效,最好進行優化。
order by的排序最好和所建索引的順序和個數一致 (2)using temporary:使用了臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
影響更大,所以要么不建索引,要么group by的順序要和索引一致
(3)using index:表示相應的select操作中使用了覆蓋索引,避免訪問了表的數據行,效率好 覆蓋索引:select后的數據列只從索引就能取得,不必讀取數據行,且與所建索引的個數(查詢列小於等於索引個數)、順序一致。 所以如果要用覆蓋索引,就要注意select的列只取需要用到的列,不用select *,同時如果將所有字段一起做索引會導致索引文件過大,性能會下降。
出現using where,表明索引被用來執行索引鍵值的查找
如果沒有同時出現using where,表明索引用來讀取數據而非執行查找動作。 (4)using where:表明使用了where過濾 (5)using join buffer:使用了連接緩存 (6)impossible where:where子句的值是false (7)select tables optimized away (8)distinct:優化distinct操作,在找到第一匹配的元組后即停止找同樣值的動作
Show Profile分析
Show Profile也是分析慢SQL的一種手段,但它能獲得比explain更詳細的信息,能分析當前會話中語句執行的資源消耗情況,能獲得這條SQL在整個生命周期的耗時,相當於執行時間的清單,也很重要。 默認關閉。開啟后,會在后台保存最近15次的運行結果,然后通過Show Profile命令查看結果。
開啟:set profiling = on;
查看:SHOW VARIABLES LIKE 'profiling%';
通過Show Profile能查看SQL的耗時
通過Query_ID可以得到具體SQL從連接 - 服務 - 引擎 - 存儲四層結構完整生命周期的耗時
可用參數type:
ALL #顯示所有的開銷信息
BLOCK IO #顯示塊IO相關開銷
CONTEXT SWITCHES #上下文切換相關開銷
CPU #顯示CPU相關開銷信息
IPC #顯示發送和接收相關開銷信息
MEMORY #顯示內存相關開銷信息
PAGE FAULTS #顯示頁面錯誤相關開銷信息
SOURCE #顯示和Source_function,Source_file,Source_line相關的開銷信息
SWAPS #顯示交換次數相關開銷的信息
出現這四個status時說明有問題,group by可能會創建臨時表
- 危險狀態:
-
converting HEAP to MyISAM #查詢結果太大,內存不夠用了,在往磁盤上搬
Creating tmp table #創建了臨時表,回先把數據拷貝到臨時表,用完后再刪除臨時表
Copying to tmp table on disk #把內存中臨時表復制到磁盤,危險!!!
locked
二進制日志解析
什么是二進制日志?
用來記錄操作MySQL數據庫中的寫入性操作(增刪改,但不包括查詢),相當於sqlserver中的完整恢復模式下的事務日志文件。
二進制日志的作用?
用於復制,配置了主從復制的時候,主服務器會將其產生的二進制日志發送到slave端,slave端會利用這個二進制日志的信息在本地重做,實現主從同步
用戶恢復,MySQL可以在全備和差異備份的基礎上,利用二進制日志進行基於時間點或者事物Id的恢復操作。原理同於主從復制的日志重做。
可以用作數據分發,有收費的工具可以把一個數據庫的數據分發到其他的mysql數據庫,在事務性不強的統計等其他方面的需求可以使用分發庫,請注意分發庫會有一定的延遲(正常延遲是毫秒級)。
Mysqlbinlog命令行工具可以對二進制日志進行操作。二進制日志很重要,請務必開啟。
MySQL的讀寫分離
Mysql的讀寫分離正常情況下只支持一主多從。
讀寫分離應用中間件來支持(盡量不要使用多數據源的方式):
可以支持SQLhint路由,可以根據規則引入確認的一個節點上
可以把事務全部到主節點上(如果不這樣做讀在從節點上,寫在主節點上會出現臟數據,因為主從同步是有少許延遲的,雖然是毫秒級的)
不再詳細描述
索引的注意事項
索引對查詢是速度提升以及減低慢SQL的查詢是非常必要的,但是索引建立的復雜度高的,會影響插入和更新的效率,但是不要為了插入和更新的效率而不建立索引或者少建立索引,因為插入和更新在沒有表鎖的情況下基本沒有慢SQL。
但是如果一定想要屏蔽插入和更新因為索引帶來的效率下降有以下方式(中間件方式): 分庫分表,減低單表數據量(數據量少了索引也就小了),這樣因為插入和更新造成的索引更新效率就變的影響非常的小。
讀寫分離,主庫(主要負責插入和更新)減少或者弱化索引的數量,從庫(主要負責讀取)增加索引的組合方式和數量,從庫還可以是多個,不同的從庫在同一個表建立的索引可以不一樣,這樣可以通過路由的方式把不同規則的語句路由到不同的從庫上。
例如: 數據庫DB,有2個只讀節點DBR1和DBR2。對table中有A、B、C三個列,對這3個列進行的索引。正常來說數據庫table表中如果是AB一個組合索引,ABC一個組合索引,BC一個組合索引就會讓索引變的比較臃腫,並且SQL在解析的時候還有可能用錯索引。 DBR1只建立ABC索引,DBR2建立AB索引,就變的比較清晰了。
建立索引不要太在意網上的那些方式,包括 in 語句無法用到索引等,要相信執行計划(explain)而不要相信直覺,因為數據庫不斷的升級,一個版本都會有部分的差異。
建立組合索引的原則可以基本的理解為, where 條件后有幾個and條件,這些條件就應該建立一個組合索引,有些and條件是可變的或者拼接的,所以索引不要這樣盲目建立,需要根據慢sql來建立。 高並發正在運行的生成系統建立索引需要注意的步驟: 1、打開查詢編輯器輸入,show full processlist 2、打了另外的編輯器輸入建立索引的語句 3、執行建立索引的語句 4、切換第一個查詢編輯器不斷的運行show full processlist,查看目前所有會話執行sql的狀態,如果出現大量的緩慢SQL現象以及死鎖現象,盡快用kill語句殺死一直在運行的會話(不只是這個建立索引的會話)。因為失敗,建立索引需要再次規划時間,正常情況下應該是凌晨進行。
可以使用函數等方式強制只用某個索引或者取消使用索引:
SELECT * FROM TABLE1 FORCE INDEX (FIELD1) …
SELECT * FROM TABLE1 IGNORE INDEX (FIELD1, FIELD2) …
數據庫設計范式
第一范式
第一范式是最基本的范式。如果數據庫表中的所有字段值都是不可分解的原子值,就說明該數據庫滿足第一范式。 第一范式的合理遵循需要根據系統給的實際需求來確定。比如某些數據庫系統中需要用到"地址"這個屬性,本來直接將"地址"屬性設計成為一個數據庫表的字段就行,但是如果系統經常訪問"地址"屬性中的"城市"部分,那么一定要把"地址"這個屬性重新拆分為省份、城市、詳細地址等多個部分來進行存儲,這樣對地址中某一個部分操作的時候將非常方便,這樣設計才算滿足數據庫的第一范式。如下圖。
上圖所示的用戶信息遵循第一范式的要求,這樣對用戶使用城市進行分類的時候就非常方便,也提高了數據庫的性能。
第二范式
第二范式在第一范式的基礎上更進一層,第二范式需要確保數據庫表中每一列都和主鍵相關,而不能只與主鍵的某一部分相關(主要針對聯合主鍵而言)。也就是說在一個數據庫表中,一個表中只能保存一種數據,不可以把多種數據保存在同一張數據庫表中。 比如要設計一個訂單信息表,因為訂單中可能會有多種商品,所以要將訂單編號和商品編號作為數據庫表的聯合主鍵,如下圖。
這里產生一個問題:這個表中是以訂單編號和商品編號作為聯合主鍵,這樣在該表中商品名稱、單位、商品價格等信息不與該表的主鍵相關,而僅僅是與商品的編號相關,所以在這里違反了第二范式的設計原則。 而如果把這個訂單信息表進行拆分,把商品信息分離到另一個表中,把訂單項目表也分離到另一個表中,就非常完美了,如下圖。
這里這樣設計,在很大程度上減小了數據庫的冗余,如果要獲取訂單的商品信息,使用商品編號到商品信息表中查詢即可。
第三范式
第三范式需要確保數據表中的每一列數據都和主鍵直接相關,而不能間接相關。 比如在設計一個訂單數據表的時候,可以將客戶編號作為一個外鍵和訂單表建立相應的關系,而不可以在訂單表中添加關於客戶其他信息(比如姓名、所屬公司)的字段,如下面這兩個表所示的設計就是一個滿足第三范式的數據庫表。
這樣在查詢訂單信息的時候,就可以使用客戶編號來引用客戶信息表中的記錄,也不必再訂單信息表中多次輸入客戶信息的內容,減小了數據冗余。