MySQL查詢一句,也是執行很慢的原因


 

1、概念

哪些情況下,執行一條語句,查詢執行的也是很緩慢呢?

為了便於描述,我還是構造一個表,基於這個表來說明今天的問題。這個表有兩個字段id和c,並且我在里面插入了10萬行記錄。

 

mysql> CREATE TABLE `t` (

  `id` int(11) NOT NULL,

  `c` int(11) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB;

 

delimiter ;;

create procedure idata()

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();

2、第一類:查詢長時間不返回

mysql> select * from t where id=1;

 

查詢結果長時間不返回。

一般碰到這種情況的話,大概率是表t被鎖住了。接下來分析原因的時候,一般都是首先執行一下show processlist命令,看看當前語句處於什么狀態。

 

然后我們再針對每種狀態,去分析它們產生的原因、如何復現,以及如何處理。

 

 

(1)等MDL鎖

就是使用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 命令斷開即可。

 

 

(2)等flush

在表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語句。

 

(3)等行鎖

 

可以通過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上的行鎖。

3、第二類,查詢慢

 

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的目的是方便你看圖。

 

4、小結

執行“查一行”,可能會出現的被鎖住和執行慢的例子。這其中涉及到了表鎖、行鎖和一致性讀的概念。

 

在實際使用中,碰到的場景會更復雜。但大同小異,可以按照定位方法,來定位並解決問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM