【MySQL】metadata lock問題

1、Metadata lock

MySQL使用DML來管理對數據庫對象的併發訪問,並確保數據一致性。DML不只適用於表,還適用於模式和存儲程序(過程、函數、觸發器和計劃的事件)mysql

1.1 MDL簡述

爲了在併發環境下維護表元數據的數據一致性,在表上有活動事務(顯式或隱式)的時候,不能夠對元數據進行寫入操做。所以從MySQL5.5版本開始引入了MDL鎖,來保護表的元數據信息,用於解決或者保證DDL操做與DML操做之間的一致性。sql

元數據鎖的獲取不依賴於使用的引擎,不管使用的是設置autocommit=0的MyISAM引擎仍是用begin或start transaction語句顯示聲名的事務,鏈接都會獲取元數據鎖。一旦出現Metadata Lock Wait等待現象,後續全部對該表的訪問都會阻塞在該等待上,致使鏈接堆積,業務受影響。數據庫

1.2 MDL解決的問題

Metadata lock 是MySQL在5.5.3版本後引入了,爲的是防止5.5.3之前的一個bug的出現:併發

當一個會話在主庫執行DML操做還沒提交時,另外一個會話對同一個對象執行了DDL操做如drop table,而因爲MySQL的binlog是基於事務提交的前後順序進行記錄的,所以在slave上應用時,就出現了先drop table,而後再向table中insert的狀況,致使從庫應用出錯。函數

對於引入MDL,其主要解決了2個問題:編碼

一個是事務隔離問題,好比在可重複隔離級別下,會話A在2次查詢期間,會話B對錶結構作了修改,兩次查詢結果就會不一致,沒法知足可重複讀的要求;code

另一個是數據複製的問題,好比會話A執行了多條更新語句期間,另一個會話B作了表結構變動而且先提交,就會致使slave在重作時,先重作alter,再重作update時就會出現複製錯誤的現象。因此在對錶進行上述操做時,若是表上有活動事務(未提交或回滾),請求寫入的會話會等待在Metadata lock wait 。orm

2、常見MDL鎖場景:

①當前有執行DML操做(DML未執行完成)時,執行DDL操做對象

② 當前有對錶的長時間查詢或使用mysqldump/mysqlpump時,執行DDL會被堵住進程

③ 顯示或者隱式開啓事務後未提交或回滾,好比查詢完成後未提交或者回滾,DDL會被堵住

④ 表上有失敗的查詢事務,好比查詢不存在的列,語句失敗返回,可是事務沒有提交,此時DDL仍然會被堵住

3、例子

mysql版本:5.6.29

隔離級別:READ COMMITTED

3.1 場景1

(1) 現象模擬

事務1 事務2 事務3
begin;
select * from base_code;
- alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼';——執行被阻塞
- - select * from base_code;——執行被阻塞

(2) show processlist查看結果以下:

mysql> show processlist;
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
| Id    | User            | Host      | db      | Command | Time     | State                           | Info                                                                               |
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
|     1 | event_scheduler | localhost | NULL    | Daemon  | 20309270 | Waiting on empty queue          | NULL                                                                               |
| 98456 | root            | localhost | lcl_abc | Sleep   |       85 |                                 | NULL                                                                               |
| 98459 | root            | localhost | lcl_abc | Query   |       79 | Waiting for table metadata lock | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼'   |
| 98461 | root            | localhost | lcl_abc | Query   |       51 | Waiting for table metadata lock | select * from base_code                                                            |
| 98462 | root            | localhost | NULL    | Query   |        0 | init                            | show processlist                                                                   |
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)

select * from base_code; 再次查詢base_code表也是 Waiting for table metadata lock狀態,說明因爲 metadata lock的存在,會致使後面正常的查詢都會由於等待鎖而阻塞。

若是先執行事務3,是能夠查詢的。執行完事務2,形成阻塞後,纔會阻塞後續全部的操做。

(3) 查看當前事務運行狀態:

mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
                    trx_id: 339395
                 trx_state: RUNNING
               trx_started: 2020-04-14 16:51:43
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 98456
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 488
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: READ COMMITTED
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 10000
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.17 sec)

能夠看到,事務1因爲還沒提交,因此這裏能看到它的狀態仍是running.
可是這裏咱們看不到正在執行的語句,不知道究竟是什麼語句致使的。

(4) 查看該事務對應的進程

mysql> select * from information_schema.processlist where id=98456;
+-------+------+-----------+---------+---------+------+-------+------+
| ID    | USER | HOST      | DB      | COMMAND | TIME | STATE | INFO |
+-------+------+-----------+---------+---------+------+-------+------+
| 98456 | root | localhost | lcl_abc | Sleep   | 1107 |       | NULL |
+-------+------+-----------+---------+---------+------+-------+------+
1 row in set (0.00 sec)

只能根據trx_mysql_thread_id看到未提交的事務的process id,看一下processlist,INFO內也沒有具體內容。
此時能夠經過performance_schema.events_statements_current來查看到對應的sql,包括已經執行完,但沒有提交的。

mysql> SELECT b.processlist_id, c.db, a.sql_text, c.command, c.time, c.state FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id) JOIN information_schema.processlist c ON b.processlist_id = c.id WHERE a.sql_text NOT LIKE '%performance%';
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
| processlist_id | db      | sql_text                                                                           | command | time | state                           |
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
|          98459 | lcl_abc | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼'   | Query   |  636 | Waiting for table metadata lock |
|          98461 | lcl_abc | select * from base_code                                                            | Query   |  632 | Waiting for table metadata lock |
|          98456 | lcl_abc | select * from base_code                                             | Sleep   |  639 |                                 |
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
3 rows in set (0.05 sec)

(5) 提交或關閉形成DML鎖的進程

提交或者kill 98456後,能夠看到事務二、事務3立馬執行完了

mysql> alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼';
Query OK, 7 rows affected (21 min 58.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

3.2 場景2:

事務1,開啓事務,執行語句報錯,其餘語句獲取到的鎖在這個事務提交或回滾以前,仍然不會釋放掉

(1) 現象模擬

事務1 事務2 事務3
begin;
update base_code set num2=1 where id=1;——ERROR 1054 (42S22): Unknown column 'num2' in 'field list'
- alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼';——執行被阻塞
- - select * from base_code;——執行被阻塞

因爲num2字段不存在,事務1執行報錯,致使update執行失敗,可是沒有提交該事務,此時依然會形成alter語句阻塞,之後後續的select阻塞。

(2)show processlist查看結果以下:

mysql> show processlist;
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
| Id    | User            | Host      | db      | Command | Time     | State                           | Info                                                                               |
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
|     1 | event_scheduler | localhost | NULL    | Daemon  | 20314642 | Waiting on empty queue          | NULL                                                                               |
| 98456 | root            | localhost | lcl_abc | Sleep   |     1030 |                                 | NULL                                                                               |
| 98459 | root            | localhost | lcl_abc | Query   |      983 | Waiting for table metadata lock | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼'   |
| 98461 | root            | localhost | lcl_abc | Query   |        3 | Waiting for table metadata lock | select * from base_code                                                            |
| 98462 | root            | localhost | NULL    | Query   |        0 | init                            | show processlist                                                                   |
+-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)

(3) 查看當前事務運行狀態:

在information_schema.innodb_trx中也沒有任何進行中的事務.需查詢表performance_schema.events_statements_current,該表能夠看到對應的sql,包括已經執行完,但沒有提交的

缺陷:一個事務可能有一組sql組成,這個方法只能看到這個事務最後執行的是什麼SQL,沒法看到所有。(假如事務1,執行完update後又執行了一個select,則events_statements_current表中只能看到最後執行的select語句)

mysql> SELECT b.processlist_id, c.db, a.sql_text, c.command, c.time, c.state FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id) JOIN information_schema.processlist c ON b.processlist_id = c.id WHERE a.sql_text NOT LIKE '%performance%';
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
| processlist_id | db      | sql_text                                                                           | command | time | state                           |
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
|          98459 | lcl_abc | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '編碼'   | Query   |  636 | Waiting for table metadata lock |
|          98461 | lcl_abc | select * from base_code                                                            | Query   |  632 | Waiting for table metadata lock |
|          98463 | lcl_abc | update base_code set num2=1 where id=1                                             | Sleep   |  639 |                                 |
+----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+
3 rows in set (0.05 sec)

4、參數

能夠經過 lock_wait_timeout 變量來指定超時時間,默認是31536000秒(一年),因此鎖住的查詢永遠不會終止。

5、總結

  • 爲了事務的串行話,和數據一致性, Mysql會對打開事務進行DML的表加上table metadata lock,在事務提交前,其餘的DDL操做會阻塞
  • 對於主要是查詢數據的項目來講,默認不開啓事務便可,若是確實須要,程序上手動開啓事務
  • 須要使用到事務時,也要儘可能縮小事務的運行時間,一個事務中不要包含太多的語句
  • 程序上對任何錯誤異常情況必定要捕捉後,回滾事務,不然事務脫離程序,只能等事務本身超時,手動關閉事務或者重啓服務釋放鎖了

6、查找未提交事務的sql的方法

(1) 表performance_schema.events_statements_current

缺陷:一個事務可能有一組sql組成,這個方法只能看到這個事務最後執行的是什麼SQL,沒法看到所有。(假如事務1,執行完update後又執行了一個select,則events_statements_current表中只能看到最後執行的select語句)

(2) general_log

即便事務沒有提交,同樣會寫到general_log.
缺陷:通常狀況下general_log不大可能打開.

(3) commit後,查看binlog

假如後面應用層最終commit了,那麼會在binlog裏記錄,能夠根據當時的tread_id去binlog查看缺陷:不會記錄select、執行失敗的語句。

相關文章
相關標籤/搜索