MySQL 的鎖也是不少,在哪種情況下會連查詢都能被阻塞?這是一個有意思的問題。
工作中,很多開發和 DBA 可能接觸較多的鎖也就行鎖了。對於行鎖,阻塞寫能理解,阻塞讀實在是想不到。能阻塞讀的那肯定是顆粒度更大的鎖了,比如表級別的。
作者:賈特特,MySQL DBA 從業者,公衆號『數據庫運維札記』作者,目前任職於某遊戲公司擔任DBA工程師
愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
本文約 2000 字,預計閱讀需要 8 分鐘。
MySQL 的鎖也是不少,在哪種情況下會連查詢都能被阻塞?這是一個有意思的問題。
工作中,很多開發和 DBA 可能接觸較多的鎖也就行鎖了。對於行鎖,阻塞寫能理解,阻塞讀實在是想不到。能阻塞讀的那肯定是顆粒度更大的鎖了,比如表級別的。
本文操作環境爲 MySQL 8.0。
MySQL 表級鎖有兩種實現
- 服務器(SERVER)層:本層的鎖定主要是元數據鎖(metadata lock,MDL)。
- 存儲引擎(ENGINE)層:本層不同的存儲引擎可能會實現不同的鎖定策略。例如 MyISAM 引擎實現了表級鎖,InnoDB 存儲引擎實現了行級鎖和表級鎖,其中表級鎖是通過意向鎖體現的。
元數據鎖(MDL)是由 SERVER 層管理,用於鎖定數據庫對象的元數據信息,如:表結構、索引等。元數據鎖可以阻止對錶結構的改變,以確保數據定義的一致性。
元數據鎖的類型
每種鎖類型後面會詳細介紹。簡單來說,對於元數據鎖而言,當對一個表進行增刪改查操作的時候,會加 元數據讀鎖。當對錶數據結構進行變更的時候會加 元數據寫鎖。它讀寫互斥,寫寫互斥,只有讀讀不衝突。
意向鎖是在存儲引擎層實現的,用於協調不同事務對錶級鎖和行級鎖的請求。當一個事務在某個層次(表級或行級)上獲取鎖時,會首先獲取對應層次的意向鎖,以提示其他事務該事務在該層次上有鎖的意向。這樣可以在更高層次上減少鎖衝突,提高併發性能。
InnoDB 存儲引擎的意向鎖種類
- 意向共享鎖(Intention Shared Lock,IS):事務打算給數據行加共享鎖(S 鎖)。
- 意向排他鎖(Intention Exclusive Lock,IX):事務打算給數據行加排他鎖(X 鎖)。
這樣看來,表對象不可讀寫有種情況可能就是元數據鎖互斥所導致的。
Waiting for table metadata lock
本節中未完成的讀寫事務,在實際中可能是未完成的大事務,也可能是未顯式結束的事務。
元數據鎖互斥(未完成的讀事務)
會話 1 執行:有未完成的讀事務,此時獲取了元數據共享讀鎖。
MDL_SHARED_READ: 這個鎖允許會話讀取表的數據,並允許其他會話獲取 SHARED_READ 或 SHARED_WRITE 鎖,但不允許獲取 SHARED_NO_READ_WRITE 或 EXCLUSIVE 鎖。
會話 2 執行:ALTER 表結構變更語句,此時 ALTER 語句要獲取元數據排它鎖。
MDL_EXCLUSIVE: 這個鎖允許會話讀取和修改表的數據和結構,但不允許其他會話獲取任何類型的鎖。
元數據鎖互斥等待,之後其他會話對於所涉及表不可讀寫。
元數據鎖互斥(未完成的寫事務)
會話 1 執行:有未完成的寫事務,此時獲取了元數據寫鎖。
MDL_SHARED_WRITE: 這個鎖允許會話讀取和修改表的數據,並允許其他會話獲取 SHARED_READ 鎖,但不允許獲取 SHARED_WRITE、SHARED_NO_READ_WRITE 或 EXCLUSIVE 鎖。
會話 2 執行:ALTER 表結構變更語句,此時 ALTER 語句要獲取元數據排它鎖。
**MDL_EXCLUSIVE:**這個鎖允許會話讀取和修改表的數據和結構,但不允許其他會話獲取任何類型的鎖。
元數據鎖互斥等待,之後其他會話對於所涉及表不可讀寫。
LOCK TABLES ... READ/WRITE
LOCK TABLES 可以顯式獲取表鎖,需要注意的是會話只能自己獲取和釋放表鎖。UNLOCK TABLES 可以顯式釋放當前會話的表鎖。
LOCK TABLES … READ
會話 1 執行:lock tables db_version read;
MDL_SHARED_READ_ONLY: 這個鎖允許會話讀取表的數據,並允許其他會話獲取 SHARED_READ 鎖,但不允許獲取 SHARED_WRITE、SHARED_NO_READ_WRITE 或 EXCLUSIVE 鎖。
此時 db_version
加了元數據共享只讀鎖。
會話 2 執行:ALTER 表結構變更語句,此時 ALTER 語句要獲取元數據排它鎖,元數據鎖互斥等待。
之後所涉及表對象將不可讀寫。
LOCK TABLES … WRITE
會話 1 執行:lock tables db_version write;
MDL_SHARED_NO_READ_WRITE: 這個鎖允許當前會話讀取和修改表的數據,但不允許其他會話獲取任何類型的鎖。
此時 db_version
加上了獨佔寫鎖。只能在 會話 1 讀寫,它會阻止其他會話獲取任何類型的鎖。
因此其他會話既不能讀也不能寫,當然查詢也會被阻塞了。
需要注意的是,此時 會話 1 對其他表也會不可讀寫。
FLUSH TABLES & WAITING FOR TABLE FLUSH
FLUSH TABLES 關閉所有打開的表,強制關閉所有正在使用的表,並刷新準備好的語句緩存。當存在活動的 LOCK TABLES 時,不允許執行 FLUSH TABLES 操作。
FLUSH TABLES
- 當 ALTER 表結構時,執行 FLUSH TABLES 阻塞,從而導致表對象不可讀寫。
-
當 LOCK TABLES 後,執行 FLUSH TABLES 會被阻塞,從而導致表對象不可讀寫。
- 會話 1 執行:
lock tables db_version read;
- 會話 2 執行:
flush tables;
- 會話 1 執行:
此時,會話 2 會被阻塞,其他會話對所涉及表將不可讀寫。SHOW PROCESSLIST 中會提示 Waiting for table flush
。
需要說明的是,會話 1 執行完 lock tables...read lock
後,其他會話執行 DML 增刪改語句,雖然會因獲取不到元數據鎖而阻塞,但不會阻塞其他會話執行 SELECT 查詢。
換言之,執行 lock tables...read
後,當遇到元數據鎖排它鎖互斥阻塞(ALTER 語句)或者 FLUSH TABLES 發生阻塞後,纔會發生所涉及表對象不可讀寫。
處理延伸
如何處理並找到源頭 SQL 呢?
對於因元數據鎖互斥而導致的表不可讀寫,一般可以通過 sys
庫下的內置視圖來查看。可能會涉及的表:
sys.schema_table_lock_waits: 可直接通過 sys
下內置視圖,看到元數據鎖互斥的相關信息。
information_schema.innodb_trx: 找到長時間未提交的事務。
對於因 FLUSH TABLE 等待而導致的表不可讀寫的場景,通過上述視 圖/表 是不一定有數據的。大致會有以下兩種情況:
Waiting for table flush: 可以按如下方式尋找源頭。這種情況主要出現於因 FLUSH TABLES 而等待後,執行 DML 語句。
SELECT
b.PROCESSLIST_ID,
b.THREAD_ID,
a.OBJECT_NAME,
a.LOCK_TYPE,
a.LOCK_STATUS,
b.PROCESSLIST_STATE
FROM
`performance_schema`.metadata_locks a
LEFT JOIN `performance_schema`.threads b ON a.OWNER_THREAD_ID = b.THREAD_ID
WHERE
a.OBJECT_SCHEMA = 'tmp';
也可以通過線程 ID 找到會話最近的 10 條語句進一步判斷確認。
select THREAD_ID,event_id,sql_text from
`performance_schema`.events_statements_history
where THREAD_ID = 14503
order by event_id;
Waiting for table metadata lock: 可以參考元數據鎖互斥而導致的表不可讀寫處理。這種情況主要出現於因 FLUSH TABLES 而等待後,執行 DDL 語句如 ALTER TABLE。
總結
以下情況會導致表對象不可讀寫:
- 因 Waiting for table metadata lock 而導致的表對象不可讀寫。
- 因 Waiting for table flush 而導致的表對象不可讀寫。
更多技術文章,請訪問:https://opensource.actionsky.com/
關於 SQLE
SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。