哪些狀況下,執行一條語句,查詢執行的也是很緩慢呢?mysql
爲了便於描述,我仍是構造一個表,基於這個表來講明今天的問題。這個表有兩個字段id和c,而且我在裏面插入了10萬行記錄。sql
mysql> CREATE TABLE `t` (session
`id` int(11) NOT NULL,性能
`c` int(11) DEFAULT NULL,線程
PRIMARY KEY (`id`)3d
) ENGINE=InnoDB;日誌
delimiter ;;orm
create procedure idata()blog
begin索引
declare i int;
set i=1;
while(i<=100000)do
insert into t values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
mysql> select * from t where id=1;
查詢結果長時間不返回。
通常碰到這種狀況的話,大機率是表t被鎖住了。接下來分析緣由的時候,通常都是首先執行一下show processlist命令,看看當前語句處於什麼狀態。
而後咱們再針對每種狀態,去分析它們產生的緣由、如何復現,以及如何處理。
就是使用show processlist命令查看Waiting for table metadata lock的示意圖。
可是,因爲在show processlist的結果裏面,session A的Command列是「Sleep」,致使查找起來很不方便。不過有了performance_schema和sys系統庫之後,就方便多了。(MySQL啓動時須要設置performance_schema=on,相比於設置爲off會有10%左右的性能損失)
經過查詢sys.schema_table_lock_waits這張表,咱們就能夠直接找出形成阻塞的process id,把這個鏈接用kill 命令斷開便可。
可是,因爲在show processlist的結果裏面,session A的Command列是「Sleep」,致使查找起來很不方便。不過有了performance_schema和sys系統庫之後,就方便多了。(MySQL啓動時須要設置performance_schema=on,相比於設置爲off會有10%左右的性能損失)
經過查詢sys.schema_table_lock_waits這張表,咱們就能夠直接找出形成阻塞的process id,把這個鏈接用kill 命令斷開便可。
在表t上,執行下面的SQL語句:
mysql> select * from information_schema.processlist where id=1;
查出來這個線程的狀態是Waiting for table flush
這個狀態表示的是,如今有一個線程正要對錶t作flush操做。MySQL裏面對錶作flush操做的用法,通常有如下兩個:
flush tables t with read lock;
flush tables with read lock;
這兩個flush語句,若是指定表t的話,表明的是隻關閉表t;若是沒有指定具體的表名,則表示關閉MySQL裏全部打開的表。
可是正常這兩個語句執行起來都很快,除非它們也被別的線程堵住了。
因此,出現Waiting for table flush狀態的可能狀況是:有一個flush tables命令被別的語句堵住了,而後它又堵住了咱們的select語句。
能夠經過sys.innodb_lock_waits 表查到。
查詢方法是:
mysql> select * from t sys.innodb_lock_waits where locked_table=`'test'.'t'`\G
能夠看到,這個信息很全,4號線程是形成堵塞的罪魁禍首。而幹掉這個罪魁禍首的方式,就是KILL QUERY 4或KILL 4。
不過,這裏不該該顯示「KILL QUERY 4」。這個命令表示中止4號線程當前正在執行的語句,而這個方法實際上是沒有用的。由於佔有行鎖的是update語句,這個語句已是以前執行完成了的,如今執行KILL QUERY,沒法讓這個事務去掉id=1上的行鎖。
實際上,KILL 4纔有效,也就是說直接斷開這個鏈接。這裏隱含的一個邏輯就是,鏈接被斷開的時候,會自動回滾這個鏈接裏面正在執行的線程,也就釋放了id=1上的行鎖。
mysql> select * from t where c=50000 limit 1;
因爲字段c上沒有索引,這個語句只能走id主鍵順序掃描,所以須要掃描5萬行。
做爲確認,你能夠看一下慢查詢日誌。注意,這裏爲了把全部語句記錄到slow log裏,我在鏈接後先執行了 set long_query_time=0,將慢查詢日誌的時間閾值設置爲0。
Rows_examined顯示掃描了50000行。你可能會說,不是很慢呀,11.5毫秒就返回了,咱們線上通常都配置超過1秒纔算慢查詢。但你要記住:壞查詢不必定是慢查詢。咱們這個例子裏面只有10萬行記錄,數據量大起來的話,執行時間就線性漲上去了。
掃描行數多,因此執行慢,這個很好理解。
帶lock in share mode的SQL語句,是當前讀,所以會直接讀到1000001這個結果,因此速度很快;而select * from t where id=1這個語句,是一致性讀,所以須要從1000001開始,依次執行undo log,執行了100萬次之後,纔將1這個結果返回。
注意,undo log裏記錄的實際上是「把2改爲1」,「把3改爲2」這樣的操做邏輯,畫成減1的目的是方便你看圖。
執行「查一行」,可能會出現的被鎖住和執行慢的例子。這其中涉及到了表鎖、行鎖和一致性讀的概念。
在實際使用中,碰到的場景會更復雜。但大同小異,能夠按照定位方法,來定位並解決問題。