FLUSH TABLES WITH READ LOCK
最近有一臺MySQL的從庫老是報延遲,觀察到:FLUSH TABLES WITH READ LOCK,阻塞了4個多小時,還有另外一條SQL語句select *,從現象上來看是select * 阻塞了flush tables with read lock。
flush tables with read lock,關閉所有開啟的表,同時對於所有資料庫中的表都加一個讀鎖,直到顯示地執行unlock tables,該操作常常用於資料備份的時候。也就是將所有的髒頁都要重新整理到磁碟,然後對所有的表加上了讀鎖,於是這時候直接拷貝資料檔案也就是安全的。但是如果你發出命令flush tables with read lock時,還有其他的操作,而起是很耗時的操作呢?先說寫操作,這個FTWRL肯定是得等的,等寫操作完成才能執行FTWRL,這個很好理解。那麼對於其他的讀操作呢?比如說在FLWRL發出之前有一個query:select count(*) from tb,那麼FTWRL也得等待(show processlist可以看到 waiting for table flush)。你可能會說在mysql中讀與讀不是不會排斥的嗎,為什麼需要等待呢?因為FTWRL是要flush髒頁的,只有這樣才真的能保證資料一致性(比如說在xtrabackup備份myisam表的時候),而在select count(*) from tb執行的時候,因為所有的操作都是在記憶體中操作,所以此時還不能完全flush,因此FTWRL就得等待。或許你還會有疑問,select的頁不是髒頁,為什麼FTWRL還要等待呢?難道mysql不能做得更完善點嗎?我覺得mysql還不是不會做的這麼簡單吧,等待的原因是因為這個表很大,無法一次性將所有的頁都讀到記憶體中來,而query具有原子性,總不可能執行一般被堵塞吧,所以說還是得乖乖的讓它執行然,所以FTWRL就得等待了。
flush tables with read lock在測試的時候,它有可能花幾毫秒就可以完成,就像我遇到的情況,在生產環境也可能花幾個小時才能完成。在此期間,MySQL服務完全block住了,而不僅僅是read-only。因為flush tables with read lock會做一下動作:
請求鎖
flush tables with read lock請求全域性read lock。當這種情況發生時,其他程序如果有修改動作的話就會被阻塞。從理論上講,這種情況並不是很糟糕,因為flush tables with read lock只需要read lock,其它命令(只需要read lock的命令)可以和flush tables with read lock並存。然而,事實上,大多數表需要讀和寫鎖的。例如:第一個寫語句會被這個全域性的讀鎖阻塞,而子查詢又會被第一個寫語句阻塞,所以真正有效果的是使用的是排它鎖,所有新請求就會被阻塞,包括讀查詢語句。
等待鎖
在flush tables with read lock成功獲得鎖之前,必須等待所有語句執行完成(包括SELECT)。所以如果有個慢查詢在執行,或者一個開啟的事務,或者其他程序拿著表鎖,flush tables with read lock就會被阻塞,直到所有的鎖被釋放。請看下面的例子:
mysql> show processlist; +----+------+-----------+------+------------+------+-------------------+----------------------------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+------------+------+-------------------+----------------------------------------------------------------------+ | 4 | root | localhost | test | Query | 80 | Sending data | select count(*) from t t1 join t t2 join t t3 join t t4 where t1.b=0 | | 5 | root | localhost | test | Query | 62 | Flushing tables | flush tables with read lock | | 6 | root | localhost | test | Field List | 35 | Waiting for table | | | 7 | root | localhost | test | Query | 0 | NULL | show processlist | +----+------+-----------+------+------------+------+-------------------+----------------------------------------------------------------------+ 4 rows in set (0.00 sec)
可以看到執行緒6沒有連進來,因為MySQL的客戶端連線時沒有指定-A,它嘗試獲取當前庫下的所有的表和列。執行緒5也沒有flush tables,因為它在等執行緒4釋放鎖。
重新整理表
當flush tables with read lock拿到鎖後,必定flush data。對於MyISAM引擎,不光是重新整理它自己的data,也重新整理作業系統的data到disk上(MyISAM relies on the filesystem block cache for caching reads to the data rows and indexes, while InnoDB does this within the engine itself, combining the row caches with the index caches),所以如果是MyISAM表的話有可能會花費很長時間。
持有鎖
我們可以使用unlock tables或者其它命令來釋放鎖。
結論
一個備份系統一般都是在生產環境中用的,所以我們不能簡單的認為flush tables with read lock很快就執行完。在某些情況下,執行慢是沒法避免的。但是我們可以配置備份系統避免這種global lock。