1、查詢長時間不返回:
在表 t 執行下面的 SQL 語句:
mysql> select * from t where id=1;
查詢結果長時間不返回。
一般碰到這種情況的話,大概率是表 t 被鎖住了。接下來分析原因的時候,一般都是首先執行一下 show processlist 命令,看看當前語句處於什么狀態。然后我們再針對每種狀態,去分析它們產生的原因、如何復現,以及如何處理。等 MDL 鎖如下圖所示,就是使用 show processlist 命令查看 Waiting for table metadata lock 的示意圖。
出現這個狀態表示的是,現在有一個線程正在表 t 上請求或者持有 MDL 寫鎖,把 select 語句堵住了。
不過,在 MySQL 5.7 版本下復現這個場景,也很容易。如圖 3 所示,我給出了簡單的復現步驟。
session A 通過 lock table 命令持有表 t 的 MDL 寫鎖,而 session B 的查詢需要獲取 MDL 讀鎖。所以,session B 進入等待狀態。這類問題的處理方式,就是找到誰持有 MDL 寫鎖,然后把它 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、等行鎖:
現在,經過了表級鎖的考驗,我們的 select 語句終於來到引擎里了。
mysql> select * from t where id=1 lock in share mode;
由於訪問 id=1 這個記錄時要加讀鎖,如果這時候已經有一個事務在這行記錄上持有一個寫鎖,我們的 select 語句就會被堵住。復現步驟和現場如下:
show processlist 命令查看
顯然,session A 啟動了事務,占有寫鎖,還不提交,是導致 session B 被堵住的原因。這個問題並不難分析,但問題是怎么查出是誰占着這個寫鎖。如果你用的是 MySQL 5.7 版本,可以通過 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 上的行鎖。
PS:
select * from information_schema.innodb_trx 或者 select * from information_schema.innodb_trx\G;
其中字段解釋如下:
- trx_state: 事務狀態,一般為RUNNING
- trx_started: 事務執行的起始時間,若時間較長,則要分析該事務是否合理
- trx_mysql_thread_id: MySQL的線程ID,用於kill
- trx_query: 事務中的sql