鎖的調試

                   鎖的調試

目錄

背景

服務器級別的鎖等待

找出誰持有鎖

InnoDB中的鎖等待

使用 INFORMATION_SCHEMA表


背景

當我們給某個表增加一列新字段,或只是進行查詢,就有可能發現其他請求鎖住了操作的表或者行。
此時,需要完成兩個操作:1、找出查詢阻塞的原因; 2、知道該殺死哪個進程。

服務器級別的鎖等待

鎖等待可能發生在服務器級別或存儲引擎級別。
mysql服務器使用的幾種類型的鎖如下:
1、表鎖:表可以被顯式的讀鎖和寫鎖進行鎖定。顯式的鎖用LOCK TABLES 和UNLOCK TABLES來控制,也可以隱式的創建鎖。
LOCK TABLES sakila.film READ; // 在一個會話中執行時,在sakila.film獲得一個顯式的鎖
LOCK TABLES sakia.film WRITE; // 再在另外一個會話中執行時,查詢會掛起並且不會完成
SHOW PROCESSLIST; // 可以在第一個會話中看到等待線程, State的狀態爲Locked
用一個長時間運行的查詢可以觸發隱式地鎖住表,比如用SLEEP()函數
SELECT SLEEP(30) FROM sakia.film LIMIT 1;
2、全局鎖:可以通過FlUSH TABLES WITH READ LOCK; 或設置read_only=1 老獲取單個全局讀鎖。它與任何表鎖都衝突。要判斷一個查詢正在等待全局讀鎖而不是一個表級別的鎖,可以通過SHOW PROCESSLIST的輸出, State:Waiting for release of readlock.
3、命名鎖:表鎖的一種,服務器在重命名或刪除一個表時創建。命名鎖與普通的表鎖相沖突。
RENAME TABLE sakila.film2 TO sakila.film;
可以在SHOW OPEN TABLES; 輸出中看到命名鎖的影響。
4、用戶鎖:可以用GET_LOCK() 機器相關函數在服務器級別內鎖住和釋放任何一個字符串。需要制定鎖的名稱字符串,以及等待超時秒數。
SELECT GET_LOCK('my lock', 100);

找出誰持有鎖

在某些場景下,可以清楚地看到幾個連接長時間持有某個鎖,此時需要將它們殺死。sql命令無法顯示哪個進程有阻塞你查詢的表鎖。mysqladmin工具有個debug命令可以打印關於鎖的信息到服務器的錯誤日誌中。

$ mysqladmin debug

InnoDB中的鎖等待

在事務中對錶中第一行進行寫鎖。

SET AUTOCOMMIT = 0;
BEGIN;
SELECT film_id FROM sakila.film LIMIT 1 FOR UPDATE; 

如果在另外一個會話中運行相同的命令,查詢將會因第一個會話中在那一行獲取的鎖而阻塞。在SHOW INNODB STATUS的輸出中顯露了一些鎖信息。如果事務正在等待某個鎖,這個鎖會顯示在TRANSACTIONS部分中。不過看不到誰擁有鎖,很難判斷。可以用用另外一種方法:激活InnoDB鎖監控器,顯示每個事務中擁有的10把鎖。
爲了激活該監控器,需要再InnoDB存儲引擎中創建一個特殊名字的表。
create table innodb_lock_monitor(a int) ENGINE = INNODB;
若想停掉鎖監控器,刪除這個表即可。
打開SHOW INNODB STATUS查看輸出信息,能找到鎖信息。這種做法並不是最理想的,因爲鎖信息非常冗長,導出了被鎖定記錄的十六進制格式和ASCII格式。它會填滿錯誤日誌,並且還會很輕易地溢出了固定長度的輸出結果。當一個事務超過10個鎖時就無法輸出更多的鎖信息了。


使用 INFORMATION_SCHEMA表

先使用show VARIABLES檢查innodb_version變量,如果沒看到這個變量,說明還沒有使用InnoDB插件。如果沒有看到INNODB_LOCKS表,需要配置文件的plugin_load 設置中明確包括了那些表。
下面是一個顯式誰阻塞和誰在等待,一級等待多久的查詢:

select r.trx_id as waiting_trx_id, r.trx_mysql_thread_id as waiting_thread, 
timestampdiff(second, r.trx_wait_strarted, CURRENT_TIMESTAMP) as wait_time,
r.trx_query as waiting_query, l.lock_table as waiting_table_lock,
b.trx_id as blocking_trx_id, b.trx_mysql_thread_id as bolcking_thread,
substring(p.host, 1, instr(p.host, ':') - 1) as blocking_host, substring(p.host, instr(p.host, ':') + 1) as blocking_port,
if(p.command = 'Sleep', p.time, 0) as idle_in_trx, b.trx_query as blocking_query 
from INFORMATION_SCHEMA.INNODB_WAITS as w
inner join  INFORMATION_SCHEMA.INNODB_TRX as b on b.trx_id = w.blocking_trx_id
inner join  INFORMATION_SCHEMA.INNODB_TRX as r on r.trx_id = w.request_trx_id
inner join  INFORMATION_SCHEMA.INNODB_LOCKS as l on l.lock_id = w.request_id
left join  INFORMATION_SCHEMA.PROCESSLIST as p on p.id = b.trx_mysql_thread_id
ORDER BY wait_times DESC;


如果因爲線程在一個事務中空閒而正在遭受大量的鎖操作,下面查詢可以獲知多少查詢被哪些線程阻塞。

select concat('thrad', b.trx_mysql_thread_id, 'from ', p.host) as who_blocks,
if(p.command = 'Sleep', p.time, 0) as idle_in_trx, 
max(TIMESTAMPDDIFF(SECOND, r.trx_wait_started, NOW())) as max_wait_time, count(*) as num_waiters
 from INFORMATION_SCHEMA.INNODB_WAITS as w
inner join  INFORMATION_SCHEMA.INNODB_TRX as b on b.trx_id = w.blocking_trx_id
inner join  INFORMATION_SCHEMA.INNODB_TRX as r on r.trx_id = w.request_trx_id
inner join  INFORMATION_SCHEMA.INNODB_LOCKS as l on l.lock_id = w.request_id
left join  INFORMATION_SCHEMA.PROCESSLIST as p on p.id = b.trx_mysql_thread_id
group by who_blocks order by num_waiters desc ;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章