MySQL 5.5.3版本中引入了Metadata lock: DDL語句打破了事務(wù)的隔離級(jí)別
在5.5.3版本之前,MySQL事務(wù)對(duì)于表結(jié)構(gòu)元數(shù)據(jù)(Metadata)的鎖定是語句(statement)粒度的, 即語句執(zhí)行完成后,不管事務(wù)是否可以完成,其表結(jié)構(gòu)就可以被其他會(huì)話更新掉!
引入Metadata lock后,表結(jié)構(gòu)元數(shù)據(jù)(Metadata)的鎖定變成了事務(wù)(transaction)粒度的,即只有事務(wù)結(jié)束時(shí)才會(huì)釋放Metadata lock。
現(xiàn)象:
顯式開啟事務(wù)后start transaction; 該事務(wù)內(nèi)的query語句(包含select)會(huì)占用相關(guān)表的metadata lock(profile:Opening tables階段)
導(dǎo)致DDL語句被阻塞,因?yàn)楂@取不到表的metadata lock
MySQL 5.6.6版本后 優(yōu)化
metadata lock不阻塞DDL語句,但原有session再訪問此表時(shí)會(huì)返回Error信息“Table definition has changed, please retry transaction”
案列
目前創(chuàng)新互聯(lián)建站已為上千余家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)站空間、網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計(jì)、青神網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
可以看到,卡住的那一步是在rename result table,MySQL后臺(tái)將ALTER保存變成連續(xù)操作“創(chuàng)建臨時(shí)新表->插入老表的數(shù)據(jù)->臨時(shí)新表取到老表(RENAME)”,即實(shí)際的變化老表的操作在RENAME階段才發(fā)生。而這一步即是被阻塞的那一步。所以可以確認(rèn),Metadata lock影響到的是要真實(shí)修改表結(jié)構(gòu)的動(dòng)作。(回想:和pt-osc那節(jié),一個(gè)select未執(zhí)行完,alter 修改表結(jié)構(gòu)產(chǎn)生metadata lock一個(gè)道理)
基于此,我們可以驗(yàn)證ALTER TABLE,RENAME TABLE,DROP TABLE都會(huì)被Metadata lock影響,驗(yàn)證步驟與上類似,省略。
值得注意的是,創(chuàng)建一個(gè)已存在的表也會(huì)被Metadata lock影響。如果在一個(gè)事務(wù)中用到了表A的Metadata lock(如SELECT了該表)且事務(wù)未完成,那么創(chuàng)建同名的表也會(huì)被卡住。所參考的文章中提及這一點(diǎn)。有興趣可參看:討論頁 ,這里不作討論。
metadata lock的另一個(gè)副作用:如上例中,ALTER TABLE應(yīng)metadata lock被阻塞后,這導(dǎo)致后續(xù)其他事務(wù)針對(duì)該表的SELECT也會(huì)被阻塞!即ALTER TABLE的操作會(huì)影響到其他SELECT操作。根據(jù)上述示例:
Session2 mysql> alter table table1 add column c1 int;
此步一直處于等待狀態(tài)時(shí),新會(huì)話中查詢table1。
Session3 mysql> select * from test1; #一直等待
Session4 mysql>show full processlist;
mysql> show full processlist;
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------+
| 4 | root | localhost | test | Sleep | 1857 | | NULL |
| 5 | root | localhost | test | Query | 504 | Waiting for table metadata lock | select * from test1 |
| 6 | root | localhost | NULL | Query | 0 | NULL | show full processlist |
| 8 | root | localhost | test | Query | 511 | Waiting for table metadata lock | alter table test1 add column c2 int |
+----+------+-----------+------+---------+------+---------------------------------+-------------------------------------+
Session1 mysql> commit;
第一個(gè)會(huì)話提交后,Session2、Session3也成功執(zhí)行。看看Session3的select在哪個(gè)步驟需要等待:
Session3 mysql> show profile;
+——————————–+———–+
| Status | Duration |
+——————————–+———–+
| starting | 0.000018 |
| Waiting for query cache lock | 0.000003 |
| checking query cache for query | 0.000037 |
| checking permissions | 0.000007 |
| Opening tables | 22.502591 |
| System lock | 0.000013 |
| Waiting for query cache lock | 0.000024 |
| init | 0.000022 |
| optimizing | 0.000009 |
| statistics | 0.000011 |
| preparing | 0.000015 |
| executing | 0.000003 |
| Sending data | 0.000036 |
| end | 0.000009 |
| query end | 0.000005 |
| closing tables | 0.000006 |
| freeing items | 0.000007 |
| Waiting for query cache lock | 0.000003 |
| freeing items | 0.000015 |
| Waiting for query cache lock | 0.000002 |
| freeing items | 0.000002 |
| storing result in query cache | 0.000004 |
| logging slow query | 0.000002 |
| logging slow query | 0.000028 |
| cleaning up | 0.000003 |
+——————————–+———–+
可以看到SELECT語句首先檢查查詢緩存,沒有命中,然后在Opening tables階段卡住。
這引出了另一個(gè)特點(diǎn),基于上述場景,如果后續(xù)的SELECT能夠在查詢緩存中命中,那么不會(huì)被ALTER TABLE卡住,因?yàn)椴恍枰狾pening tables操作。命中緩存SELECT的SHOW PROFILE如下:
mysql> show profile;
+——————————–+———-+
| Status | Duration |
+——————————–+———-+
| starting | 0.000017 |
| Waiting for query cache lock | 0.000004 |
| checking query cache for query | 0.000006 |
| checking privileges on cached | 0.000004 |
| checking permissions | 0.000007 |
| sending cached result to clien | 0.000011 |
| logging slow query | 0.000002 |
| cleaning up | 0.000003 |
+——————————–+———-+
8 rows in set (0.00 sec)
遺留問題1.為什么第二次查詢,記錄沒有發(fā)生變化,但結(jié)果集返回空?
這個(gè)問題在這個(gè)頁面進(jìn)行了討論,確認(rèn)原因是:
MySQL中ALTER操作實(shí)際后臺(tái)是“創(chuàng)建新表”->”拷貝老表數(shù)據(jù)”->”RENAME新表成老表”。InnoDB引擎針對(duì)客戶端的REPEATABLE READ隔離級(jí)別,采用的是基于多版本(Multi-version)功能的”一致性讀”,即在事務(wù)中第一個(gè)查詢時(shí),獲取的是當(dāng)時(shí)版本的表的快照,而ALTER操作后實(shí)際是創(chuàng)建的新表,這個(gè)新表對(duì)于之前的快照而言是沒有數(shù)據(jù)的,所以第二次查詢新表基于快照的數(shù)據(jù),返回空。在MySQL 5.6.6版本后,這樣的第二個(gè)查詢會(huì)返回Error信息“Table definition has changed, please retry transaction”。詳細(xì)請(qǐng)看這里。
遺留問題2.為什么沒有鎖等待超時(shí)??
我們知道InnoDB行鎖的等待時(shí)間超過innodb_lock_wait_timeout就會(huì)報(bào)超時(shí)錯(cuò)誤。這里的metadata lock一直等待著,為什么沒有超時(shí)呢?其實(shí)metadata lock的超時(shí)設(shè)置是另一個(gè)系統(tǒng)變量lock_wait_timeout。
lock_wait_timeout控制著所有涉及到metadata lock的操作,包括DML and DDL,以及在表、視圖、存儲(chǔ)過程、存儲(chǔ)函數(shù)上的操作,以及LOCK TABLES、FLUSH TABLES WITH READ LOCK、HANDLER等。
lock_wait_timeout的默認(rèn)設(shè)置時(shí)一年,可以動(dòng)態(tài)設(shè)置,根據(jù)實(shí)際場景進(jìn)行調(diào)整。
大量參考:
http://www.mysqlperformanceblog.com/2013/02/01/implications-of-metadata-locking-changes-in-mysql-5-5/