1 問題回顧和思考
1.1 SQLException: Lock wait timeout exceeded; try restarting transaction,please rollback!mysql
再發生這樣的錯誤時,別很自豪的說數據庫出問題了,呼叫DBA ...(uat屢次出現)sql
第一個問題目前發生的緣由有:數據庫
- 磁盤空間滿,事務沒法提交成功。(磁盤盡是一個很危險的操做,會引發binlog寫壞,備庫沒法同步進而須要恢復備庫)
- 更新事務未正常提交而產生排他鎖,形成其餘更新事務一直獲取不到該鎖而事務超時。
1.2 條件查詢卡住了,怎麼重跑都通不過,怎麼辦,急死人了(遷移後比對實際出現)。session
Truncate table過程當中CTRL +C 終止了。 有分片上存在truncate 事務一直存在,進而對該表的全部操做均會超時。併發
1.3 查詢卡住,更新卡住...卻不知,你前面的Alter table都沒成功......運維
DBProxy的問題不在此文討論,查詢事務沒有正常提交而佔據共享鎖時,一樣會形成alter table獲取不到MDL鎖,而形成一直等待。 提示爲:Waiting fortable metadata lock (show processlist中可查)。socket
2 原理詳細分析
2.1 什麼是MDL鎖?分佈式
爲了在併發環境下維護表元數據的數據一致性,在表上有活動事務(顯式或隱式)的時候,不能夠對元數據進行寫入操做。所以從MySQL5.5版本開始引入了MDL鎖(metadata lock),來保護表的元數據信息,用於解決或者保證DDL操做與DML操做之間的一致性。學習
對於引入MDL,其主要解決了2個問題,一個是事務隔離問題,好比在可重複讀隔離級別下,會話A在2次查詢期間,會話B對錶結構作了修改,兩次查詢結果就會不一致,沒法知足可重複讀的要求;另一個是數據複製的問題,好比會話A執行了多條更新語句期間,另一個會話B作了表結構變動而且先提交,就會致使slave在重作時,先重作alter,再重作update時就會出現複製錯誤的現象。測試
因此在對錶進行上述操做時,若是表上有活動事務(未提交或回滾),請求寫入的會話會等待在Metadata lock wait 。例以下面的這種情形:
若沒有MDL鎖的保護,則事務2能夠直接執行DDL操做,而且致使事務1出錯,5.1版本便是如此。5.5版本加入MDL鎖就在於保護這種狀況的發生,因爲事務1開啓了查詢,那麼得到了MDL鎖,鎖的模式爲SHARED_READ,事務2要執行DDL,則需得到EXCLUSIVE鎖,二者互斥,因此事務2須要等待。
注:支持事務的InnoDB引擎表和不支持事務的MyISAM引擎表,都會出現Metadata Lock Wait等待現象。一旦出現Metadata Lock Wait等待現象,後續全部對該表的訪問都會阻塞在該等待上,致使鏈接堆積,業務受影響。
MySQL的設計:在設置的autocommit=0;read_commited的時候,不管session的第一條語句是select仍是dml,都開始一個事務,而後直到commit,所持有的MDL鎖也一直維持到commit結束。
Oracle的設計:在session的第一條更新語句發起時,才建立transaction,在讀多的系統上,減小了阻塞的發生可能性。特別是在開發人員發起select語句時,認爲沒有更新,就再也不commit。但在MySQL上,發起select語句,而忘記commit,是很是危險的。
2.2 常見MDL鎖場景和詳細解釋
1)當前有執行DML操做時執行ALTRE操做
2)當前有對錶的長時間查詢或使用mysqldump/mysqlpump時,使用alter會被堵住
3)顯示或者隱式開啓事務後未提交或回滾,好比查詢完成後未提交或者回滾,使用alter會被堵住
4)表上有失敗的查詢事務,好比查詢不存在的列,語句失敗返回,可是事務沒有提交,此時alter仍然會被堵住
詳細測試解釋說明:
1)當前有執行DML操做時執行ALTRE操做
# SESSION A mysql> insert into yetest2 select * from yetest1; # SESSION B mysql> alter table yetest2 add yeColumn int; //等待SESSION A執行完; # SESSION C mysql> show processlist; +-----+------+-------+------+--------------+--------+-----------------------------------------+------------ | Id | User | Host| db | Command | Time | State | Info |+-----+------+------+----- +--------------+--------+------------------------------------------+------------ | 267 | root | localhost | sbtest | Query | 7 | Sending data | insert into yetest2 select * from yetest1 | | 271 | root | localhost | sbtest | Query | 3 | Waiting for table metadata lock | alter table yetest2 add yeColumn int | | 272 | root | localhost | NULL | Query | 0 | starting | show processlist | +-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+ 3 rows in set (0.00 sec) # SESSION D mysql> select * from yetest2 limit 10; //等待元數據鎖; # SESSION E mysql> show processlist; +-----+------+-------+------+--------------+--------+-----------------------------------------+------------ | Id | User | Host| db | Command | Time | State | Info |+-----+------+------+----- +--------------+--------+------------------------------------------+------------ | 267 | root | localhost | sbtest | Query | 20 | Sending data | insert into sbtest2 select * from sbtest1 | | 271 | root | localhost | sbtest | Query | 13 | Waiting for table metadata lock | alter table yetest2 add yeColumn int | | 272 | root | localhost | NULL | Query | 0 | starting | show processlist | | 308 | root | localhost | sbtest | Query | 3 | Waiting for table metadata lock | select * from yetest2 limit 10 | +-----+------+-----------+--------+---------+------+---------------------------------+-------------------------------------------+ 4 rows in set (0.00 sec)
因爲事務1開啓了查詢,那麼得到了MDL鎖,鎖的模式爲SHARED_READ,事務2要執行DDL,則需得到EXCLUSIVE鎖,二者互斥,因此事務2須要等待。 查詢都能卡住,是否是很鬱悶?咱們上次遷移就是這種場景,truncate table屬於DDL,會lock table metadata,甚至能夠能夠由鎖表升級到鎖庫。
3)顯示或者隱式開啓事務後未提交或回滾,好比查詢完成後未提交或者回滾,使用alter會被堵住
# SESSION A mysql> begin; mysql> select * from test2; # SESSION B mysql> alter table test2 add test3 int; //等待SESSION A執行完; # SESSION C mysql> show processlist; +-----+------+-------+------+--------------+--------+-----------------------------------------+------------ | Id | User | Host| db | Command | Time | State | Info |+-----+------+------+----- +--------------+--------+------------------------------------------+------------ | 267 | root | localhost | sbtest | Sleep | 36 | | NULL | | 271 | root | localhost | sbtest | Query | 30 | Waiting for table metadata lock | alter table test2 add test3 int | | 272 | root | localhost | NULL | Query | 0 | starting | show processlist | +-----+------+-----------+--------+---------+------+---------------------------------+-----------------------------------+ 3 rows in set (0.00 sec)
4 ) 表上有失敗的查詢事務,好比查詢不存在的列,語句失敗返回,可是事務沒有提交,此時alter仍然會被堵住
# SESSION A mysql> begin; mysql> select error from test2; ERROR 1054 (42S22): Unknown column 'error' in 'field list' # SESSION B mysql> alter table test2 add test3 int; //等待SESSION A提交或回滾; # SESSION C mysql> show processlist; +-----+------+-------+------+--------------+--------+-----------------------------------------+------------ | Id | User | Host| db | Command | Time | State | Info |+-----+------+------+----- +--------------+--------+------------------------------------------+------------ | 267 | root | local | test | Sleep | 7 | |NULL | 271 | root | local | test | Query | 3 | Waiting for table metadata lock | alter table test2 add test3 int | | 272 | root | local | NULL| Query | 0 | starting | show processlist | 311 | root | local | NULL | Sleep | 413 | | NULL +-----+------+-----------+--------+---------+------+-------------------------------------------+-------------- 4 rows in set (0.00 sec) # SESSION D mysql> select * from information_schema.innodb_trx; Empty set (0.00 sec)
其實SESSION A中的事務並未開啓,可是因爲select獲取表元數據的語句,語法上是有效的,雖然執行失敗了,可是任然不會釋放元數據鎖,故而致使SESSION B的alter動做被阻塞。
經過SESSION D查看當前打開事務時,你會發現沒有,從而找不到緣由。因此當出現這種場景時,如何判斷是哪一個進程致使的呢,咱們能夠嘗試查看錶performance_schema. events_statements_current,分析進程狀態來進行判斷。
mysql> select * from performance_schema. events_statements_current\G *************************** 1. row *************************** THREAD_ID: 293 EVENT_ID: 32 END_EVENT_ID: 32 EVENT_NAME: statement/sql/select SOURCE: socket_connection.cc:211 TIMER_START: 212721717099954000 TIMER_END: 212721717213807000 TIMER_WAIT: 113853000 LOCK_TIME: 0 SQL_TEXT: select error from test2 DIGEST: 0bbb2d5d1be45e77debea68111264885 DIGEST_TEXT: SELECT ERROR FROM `test2` CURRENT_SCHEMA: test OBJECT_TYPE: NULL OBJECT_SCHEMA: NULL OBJECT_NAME: NULL OBJECT_INSTANCE_BEGIN: NULL MYSQL_ERRNO: 1054 RETURNED_SQLSTATE: 42S22 MESSAGE_TEXT: Unknown column 'error' in 'field list' ERRORS: 1
而後找到其sid, kill掉該session,也能夠kill掉DDL所在的session解決能夠解決此問題。
另外,測試時SESSION A要顯式開啓一個事務,不然查詢會隱式回滾結束,沒法重現上面的場景。SESSION B執行alter後,沒有當即阻塞住,而是立馬開始copy to tmp table,這個過程結束後,才進行了MDL鎖等待。執行alter操做主要分爲建立臨時新表->插入老表的數據->臨時新表rename to老表三個步驟,在這種狀況下,到最後一步才須要MDL鎖,因此copy過程當中不會阻塞。因爲沒有查詢在進行,並且查詢也沒有進入innodb層 (失敗返回),因此show processlist和information_schema.innodb_trx沒有能夠參考的信息。
出現以上幾種狀況時,這個時候若是進行以下操做就會引發MDL:
1.建立、刪除索引。
2.修改表結構。
3.表維護操做(optimize table、repair table等)。
4.刪除表。
5.獲取表上表級寫鎖 (lock table tab_name write)。
三. 總結
不少時候發生數據庫報錯時,不必定就是數據庫的問題,不必定非得急着呼叫數據庫人員解決。 咱們要造成這樣一種意識,咱們不僅是寫應用的,咱們是寫金融系統的,咱們理應具有必定的問題排查解決能力。若是隻作問題的搬運工,這是否也是對咱們自身水平的一種質疑? 只有當你們提的問題都頗有水平了,卡中心的系統纔可能穩如泰山。
固然,MySQL自己和Oracle對比存在很多的缺陷和不完善之處。而分佈式的引入又進一步引入了系統的複雜性。 這也對咱們運維人員的水平提出了更大的考驗。運維人員一樣應該踏踏實實從實踐中學習和思考,遇到問題才能從容不亂,提出行之有效的解決方法。