1. 程式人生 > MYSQL進階教學 ><p>InnoDB 儲存引擎中的鎖等待</p>

<p>InnoDB 儲存引擎中的鎖等待</p>

一般來說,儲存引擎級別的鎖,比伺服器級別的鎖更難以除錯,而且各種儲存引擎的鎖互不相同,有些儲存引擎甚至都不提供任何方法來檢視鎖。本節重點介紹 InnoDB 儲存引擎的鎖等待。

1. show engine innodb status

show engine innodb status 命令包含了 InnoDB 儲存引擎的部分鎖資訊,但很難確定哪個事務導致這個鎖的問題,因為 show engine innodb status 命令不會告訴你誰擁有鎖。

如果事務正在等待某個鎖,相關鎖資訊會體現在 show engine innodb status 輸出的 TRANSACTION 部分中。在 MySQL 會話中執行如下命令,拿到表 customer 中第一行的寫鎖。

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from customer limit 1 for update;
+------+-----------+------------+------------+--------+
| id   | last_name | first_name | birth_date | gender |
+------+-----------+------------+------------+--------+
| NULL | 111 | 222 | NULL | 1 | +------+-----------+------------+------------+--------+ 1 row in set (0.00 sec)

在 MySQL 另一個會話中,對錶 customer 執行相同的 select 命令,查詢會被阻塞。

mysql> select * from customer limit 1 for update;

這時執行 show engine innodb status 命令能夠看到相關的鎖資訊。

1 ---TRANSACTION 124178, ACTIVE 6 sec starting index read
2 mysql tables in use 1, locked 1 3 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) 4 MySQL thread id 12570, OS thread handle 139642200024832, query id 48195 localhost root Sending data 5 select * from customer limit 1 for update 6 ------- TRX HAS BEEN WAITING 6 SEC FOR THIS LOCK TO BE GRANTED: 7 RECORD LOCKS space id 829 page no 3 n bits 72 index GEN_CLUST_INDEX of table `tempdb`.`customer` trx id 124178 lock_mode X locks rec but not gap waiting

第 7 行表示 thread id 12570 這個查詢,在等待表 customer 中的 GEN_CLUST_INDEX 索引的第 3 頁上有一個排它鎖(lock_mode X)。最後,鎖等待超時,查詢返回錯誤資訊。

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

2. imformation_schema

在 MySQL5.5 開始,一般通過 imformation_schema 的表來查詢相關的事務和鎖資訊,通過imformation_schema 要比 show engine innodb status 命令要高效和全面。

在 MySQL 會話中執行如下命令,能看到誰阻塞和誰在等待,以及等待多久的查詢。

mysql> SELECT
     IFNULL(wt.trx_mysql_thread_id, 1) BLOCKING_THREAD_ID,t.trx_mysql_thread_id WAITING_THREAD_ID, CONCAT(p. USER, '@', p. HOST) USER,
     p.info SQL_TEXT, l.lock_table LOCK_TABLE, l.lock_index LOCKED_INDEX, l.lock_type LOCK_TYPE, l.lock_mode LOCK_MODE,
     CONCAT(FLOOR(HOUR (TIMEDIFF(now(), t.trx_wait_started)) / 24),'day ',MOD (HOUR (TIMEDIFF(now(), t.trx_wait_started)),24),':',
     MINUTE (TIMEDIFF(now(), t.trx_wait_started)),':',SECOND (TIMEDIFF(now(), t.trx_wait_started))) AS WAIT_TIME,
     t.trx_started TRX_STARTED, t.trx_isolation_level TRX_ISOLATION_LEVEL, t.trx_rows_locked TRX_ROWS_LOCKED, t.trx_rows_modified TRX_ROWS_MODIFIED
     FROM INFORMATION_SCHEMA.INNODB_TRX t
     LEFT JOIN information_schema.innodb_lock_waits w ON t.trx_id = w.requesting_trx_id
     LEFT JOIN information_schema.innodb_trx wt ON wt.trx_id = w.blocking_trx_id
     INNER JOIN information_schema.innodb_locks l ON l.lock_trx_id = t.trx_id
     INNER JOIN information_schema. PROCESSLIST p ON t.trx_mysql_thread_id = p.id
     ORDER BY 1\G
*************************** 1. row ***************************
 BLOCKING_THREAD_ID: 1
  WAITING_THREAD_ID: 62751
               USER: root@localhost
           SQL_TEXT: NULL
         LOCK_TABLE: `tempdb`.`customer`
       LOCKED_INDEX: GEN_CLUST_INDEX
          LOCK_TYPE: RECORD
          LOCK_MODE: X
          WAIT_TIME: NULL
        TRX_STARTED: 2020-06-22 06:52:14
TRX_ISOLATION_LEVEL: READ COMMITTED
    TRX_ROWS_LOCKED: 1
  TRX_ROWS_MODIFIED: 0
*************************** 2. row ***************************
 BLOCKING_THREAD_ID: 62751
  WAITING_THREAD_ID: 62483
               USER: root@localhost
           SQL_TEXT: select * from customer limit 1 for update
         LOCK_TABLE: `tempdb`.`customer`
       LOCKED_INDEX: GEN_CLUST_INDEX
          LOCK_TYPE: RECORD
          LOCK_MODE: X
          WAIT_TIME: 0day 0:0:5
        TRX_STARTED: 2020-06-22 07:01:49
TRX_ISOLATION_LEVEL: READ COMMITTED
    TRX_ROWS_LOCKED: 1
  TRX_ROWS_MODIFIED: 0
2 rows in set, 2 warnings (0.00 sec)

從結果顯示執行緒 62483 等待表 customer 中的鎖已經 5s,它被執行緒 62751 所阻塞。

下面這個查詢可以告訴你有多少查詢被哪些執行緒鎖阻塞。

mysql> select concat('thread ', b.trx_mysql_thread_id, ' from ', p.host) as who_blocks,
       if(p.command = "Sleep", p.time, 0) as idle_in_trx, 
       max(timestampdiff(second, r.trx_wait_started, now())) as max_wait_time, 
       count(*) as num_waiters
     from information_schema.innodb_lock_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.requesting_trx_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\G
*************************** 1. row ***************************
   who_blocks: thread 62751 from localhost
  idle_in_trx: 1206
max_wait_time: 20
  num_waiters: 5
1 row in set, 1 warning (0.00 sec)

從結果顯示執行緒 62751 已經空閒了一段時間,有 5 個執行緒在等待執行緒 62751 完成提交併釋放鎖,有一個執行緒已經等待執行緒 62751 釋放鎖長達 20s。

5. 小結

本小節以InnoDB儲存引擎為例,介紹了儲存引擎級別的鎖:show engine innodb status 和 imformation_schema。

show engine innodb status 僅包含了 InnoDB 儲存引擎的部分鎖資訊,但不會告訴你誰擁有鎖。通過imformation_schema 可以高效和全面定位到誰阻塞和誰在等待,以及等待多久的查詢。