MySQL 發行版本提供一個鎖定介面,可在兩個層級存取
在 SQL 層級,作為一組可載入函數,每個函數都對應到對服務常式的呼叫。
作為 C 語言介面,可從伺服器外掛程式或可載入函數作為外掛程式服務呼叫。
有關外掛程式服務的一般資訊,請參閱第 7.6.9 節「MySQL 外掛程式服務」。有關可載入函數的一般資訊,請參閱新增可載入函數。
鎖定介面具有以下特性
鎖定具有三個屬性:鎖定命名空間、鎖定名稱和鎖定模式
鎖定由命名空間和鎖定名稱的組合識別。命名空間允許不同的應用程式使用相同的鎖定名稱,而不會因在不同的命名空間中建立鎖定而發生衝突。例如,如果應用程式 A 和 B 分別使用
ns1
和ns2
的命名空間,則每個應用程式都可以使用鎖定名稱lock1
和lock2
,而不會干擾另一個應用程式。鎖定模式為讀取或寫入。讀取鎖定是共用的:如果工作階段對給定的鎖定識別碼具有讀取鎖定,則其他工作階段可以取得對相同識別碼的讀取鎖定。寫入鎖定是獨佔的:如果工作階段對給定的鎖定識別碼具有寫入鎖定,則其他工作階段無法取得對相同識別碼的讀取或寫入鎖定。
命名空間和鎖定名稱必須為非
NULL
、非空白,且最大長度為 64 個字元。指定為NULL
、空字串或長度超過 64 個字元的字串的命名空間或鎖定名稱會導致ER_LOCKING_SERVICE_WRONG_NAME
錯誤。鎖定介面將命名空間和鎖定名稱視為二進位字串,因此比較是區分大小寫的。
鎖定介面提供取得鎖定和釋放鎖定的函數。呼叫這些函數不需要特殊權限。權限檢查是呼叫應用程式的責任。
如果鎖定不是立即可用,則可以等待鎖定。鎖定取得呼叫會採用一個整數逾時值,表示在放棄取得鎖定之前要等待多少秒。如果在沒有成功取得鎖定的情況下達到逾時,則會發生
ER_LOCKING_SERVICE_TIMEOUT
錯誤。如果逾時為 0,則不會等待,且如果無法立即取得鎖定,則呼叫會產生錯誤。鎖定介面會偵測不同工作階段中鎖定取得呼叫之間的死鎖。在這種情況下,鎖定服務會選擇一個呼叫者,並以
ER_LOCKING_SERVICE_DEADLOCK
錯誤終止其鎖定取得要求。此錯誤不會導致交易回滾。為了在發生死鎖時選擇工作階段,鎖定服務會優先選擇持有讀取鎖定的工作階段,而不是持有寫入鎖定的工作階段。一個連線階段可以使用單一的鎖定獲取呼叫來取得多個鎖定。對於給定的呼叫,鎖定獲取是原子性的:如果所有鎖定都獲取成功,則呼叫成功。如果任何鎖定的獲取失敗,則呼叫不會取得任何鎖定並失敗,通常會出現
ER_LOCKING_SERVICE_TIMEOUT
或ER_LOCKING_SERVICE_DEADLOCK
錯誤。一個連線階段可以為相同的鎖定識別碼(命名空間和鎖定名稱組合)取得多個鎖定。這些鎖定實例可以是讀取鎖定、寫入鎖定,或兩者的混合。
在連線階段中取得的鎖定,會透過呼叫釋放鎖定函式來明確釋放,或在連線階段終止時(正常或異常)隱含釋放。鎖定不會在交易提交或回滾時釋放。
在連線階段中,當釋放給定命名空間的所有鎖定時,會一起釋放。
鎖定服務提供的介面與 GET_LOCK()
和相關 SQL 函式提供的介面不同(請參閱 第 14.14 節「鎖定函式」)。例如,GET_LOCK()
不會實作命名空間,且僅提供獨佔鎖定,而不是不同的讀取和寫入鎖定。
本節描述如何使用鎖定服務 C 語言介面。若要改用函式介面,請參閱 第 7.6.9.1.2 節「鎖定服務函式介面」。如需鎖定服務介面的一般特性,請參閱 第 7.6.9.1 節「鎖定服務」。如需關於外掛服務的一般資訊,請參閱 第 7.6.9 節「MySQL 外掛服務」。
使用鎖定服務的原始程式碼檔案應包含此標頭檔
#include <mysql/service_locking.h>
若要取得一個或多個鎖定,請呼叫此函式
int mysql_acquire_locking_service_locks(MYSQL_THD opaque_thd,
const char* lock_namespace,
const char**lock_names,
size_t lock_num,
enum enum_locking_service_lock_type lock_type,
unsigned long lock_timeout);
引數具有以下含義
opaque_thd
:執行緒控制代碼。如果指定為NULL
,則會使用目前執行緒的控制代碼。lock_namespace
:一個以 null 結尾的字串,指示鎖定命名空間。lock_names
:一個以 null 結尾的字串陣列,提供要取得的鎖定的名稱。lock_num
:lock_names
陣列中的名稱數量。lock_type
:鎖定模式,LOCKING_SERVICE_READ
或LOCKING_SERVICE_WRITE
分別取得讀取鎖定或寫入鎖定。lock_timeout
:在放棄前等待取得鎖定的整數秒數。
若要釋放為給定命名空間取得的鎖定,請呼叫此函式
int mysql_release_locking_service_locks(MYSQL_THD opaque_thd,
const char* lock_namespace);
引數具有以下含義
opaque_thd
:執行緒控制代碼。如果指定為NULL
,則會使用目前執行緒的控制代碼。lock_namespace
:一個以 null 結尾的字串,指示鎖定命名空間。
可以使用效能架構在 SQL 層級監控由鎖定服務取得或等待的鎖定。如需詳細資訊,請參閱鎖定服務監控。
本節描述如何使用其可載入函式所提供的鎖定服務介面。若要改用 C 語言介面,請參閱 第 7.6.9.1.1 節「鎖定服務 C 介面」。如需鎖定服務介面的一般特性,請參閱 第 7.6.9.1 節「鎖定服務」。如需關於可載入函式的一般資訊,請參閱 新增可載入函式。
第 7.6.9.1.1 節「鎖定服務 C 介面」中描述的鎖定服務常式不需要安裝,因為它們內建於伺服器中。對於對應到服務常式呼叫的可載入函式而言,則不是如此:函式必須在使用前安裝。本節描述如何執行此操作。如需關於可載入函式安裝的一般資訊,請參閱 第 7.7.1 節「安裝和解除安裝可載入函式」。
鎖定服務函式實作於由 plugin_dir
系統變數命名的目錄中的外掛程式庫檔案中。檔案基本名稱為 locking_service
。檔案名稱字尾因平台而異(例如,Unix 和類 Unix 系統為 .so
,Windows 為 .dll
)。
若要安裝鎖定服務函式,請使用 CREATE FUNCTION
陳述式,並根據您的平台調整 .so
字尾(如有必要)
CREATE FUNCTION service_get_read_locks RETURNS INT
SONAME 'locking_service.so';
CREATE FUNCTION service_get_write_locks RETURNS INT
SONAME 'locking_service.so';
CREATE FUNCTION service_release_locks RETURNS INT
SONAME 'locking_service.so';
如果在複寫來源伺服器上使用函式,請在所有複本伺服器上也安裝它們,以避免複寫問題。
一旦安裝,函式會保持安裝狀態,直到解除安裝為止。若要移除它們,請使用 DROP FUNCTION
陳述式
DROP FUNCTION service_get_read_locks;
DROP FUNCTION service_get_write_locks;
DROP FUNCTION service_release_locks;
在使用鎖定服務函式之前,請根據安裝或解除安裝鎖定服務函式介面中提供的指示安裝它們。
若要取得一個或多個讀取鎖定,請呼叫此函式
mysql> SELECT service_get_read_locks('mynamespace', 'rlock1', 'rlock2', 10);
+---------------------------------------------------------------+
| service_get_read_locks('mynamespace', 'rlock1', 'rlock2', 10) |
+---------------------------------------------------------------+
| 1 |
+---------------------------------------------------------------+
第一個引數是鎖定命名空間。最後一個引數是一個整數逾時,表示在放棄前等待取得鎖定的秒數。之間的引數是鎖定名稱。
對於剛才顯示的範例,函式會取得具有鎖定識別碼 (mynamespace, rlock1)
和 (mynamespace, rlock2)
的鎖定。
若要取得寫入鎖定而非讀取鎖定,請呼叫此函式
mysql> SELECT service_get_write_locks('mynamespace', 'wlock1', 'wlock2', 10);
+----------------------------------------------------------------+
| service_get_write_locks('mynamespace', 'wlock1', 'wlock2', 10) |
+----------------------------------------------------------------+
| 1 |
+----------------------------------------------------------------+
在這種情況下,鎖定識別碼為 (mynamespace, wlock1)
和 (mynamespace, wlock2)
。
若要釋放命名空間的所有鎖定,請使用此函式
mysql> SELECT service_release_locks('mynamespace');
+--------------------------------------+
| service_release_locks('mynamespace') |
+--------------------------------------+
| 1 |
+--------------------------------------+
每個鎖定函式在成功時都會傳回非零值。如果函式失敗,則會發生錯誤。例如,由於鎖定名稱不能為空,因此會發生以下錯誤
mysql> SELECT service_get_read_locks('mynamespace', '', 10);
ERROR 3131 (42000): Incorrect locking service lock name ''.
一個連線階段可以為相同的鎖定識別碼取得多個鎖定。只要不同的連線階段沒有識別碼的寫入鎖定,該連線階段就可以取得任意數量的讀取或寫入鎖定。每個針對識別碼的鎖定請求都會取得新的鎖定。以下陳述式會使用相同的識別碼取得三個寫入鎖定,然後使用相同的識別碼取得三個讀取鎖定
SELECT service_get_write_locks('ns', 'lock1', 'lock1', 'lock1', 0);
SELECT service_get_read_locks('ns', 'lock1', 'lock1', 'lock1', 0);
如果您此時檢查效能架構 metadata_locks
表格,您應該會發現該連線階段持有六個具有相同 (ns, lock1)
識別碼的個別鎖定。(如需詳細資訊,請參閱 鎖定服務監控。)
由於連線階段在 (ns, lock1)
上至少持有一個寫入鎖定,因此沒有其他連線階段可以為其取得鎖定,無論是讀取還是寫入。如果連線階段僅持有該識別碼的讀取鎖定,則其他連線階段可以為其取得讀取鎖定,但不能取得寫入鎖定。
單一鎖定獲取呼叫的鎖定是以原子方式取得,但原子性不適用於跨呼叫。因此,對於類似以下的陳述式,其中針對結果集的每一列呼叫一次 service_get_write_locks()
,原子性適用於每個個別呼叫,但不適用於整個陳述式
SELECT service_get_write_locks('ns', 'lock1', 'lock2', 0) FROM t1 WHERE ... ;
由於鎖定服務針對給定鎖定識別碼的每個成功請求傳回個別的鎖定,因此單一陳述式有可能取得大量的鎖定。例如
INSERT INTO ... SELECT service_get_write_locks('ns', t1.col_name, 0) FROM t1;
這些類型的陳述式可能會產生某些不利影響。例如,如果陳述式在執行過程中失敗並回滾,則在失敗點之前取得的鎖定仍然存在。如果目的是讓插入的列與取得的鎖定之間存在對應關係,則無法滿足此目的。此外,如果鎖定以特定順序授予很重要,請注意結果集順序可能會因最佳化工具選擇的執行計畫而異。由於這些原因,最好將應用程式限制為每個陳述式單一鎖定獲取呼叫。
鎖定服務是使用 MySQL 伺服器中繼資料鎖定架構實作的,因此您可以透過檢查效能架構 metadata_locks
表格來監控取得或等待的鎖定服務鎖定。
首先,啟用中繼資料鎖定儀器
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES'
-> WHERE NAME = 'wait/lock/metadata/sql/mdl';
然後取得一些鎖定並檢查 metadata_locks
表格的內容
mysql> SELECT service_get_write_locks('mynamespace', 'lock1', 0);
+----------------------------------------------------+
| service_get_write_locks('mynamespace', 'lock1', 0) |
+----------------------------------------------------+
| 1 |
+----------------------------------------------------+
mysql> SELECT service_get_read_locks('mynamespace', 'lock2', 0);
+---------------------------------------------------+
| service_get_read_locks('mynamespace', 'lock2', 0) |
+---------------------------------------------------+
| 1 |
+---------------------------------------------------+
mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS
-> FROM performance_schema.metadata_locks
-> WHERE OBJECT_TYPE = 'LOCKING SERVICE'\G
*************************** 1. row ***************************
OBJECT_TYPE: LOCKING SERVICE
OBJECT_SCHEMA: mynamespace
OBJECT_NAME: lock1
LOCK_TYPE: EXCLUSIVE
LOCK_STATUS: GRANTED
*************************** 2. row ***************************
OBJECT_TYPE: LOCKING SERVICE
OBJECT_SCHEMA: mynamespace
OBJECT_NAME: lock2
LOCK_TYPE: SHARED
LOCK_STATUS: GRANTED
鎖定服務鎖定的 OBJECT_TYPE
值為 LOCKING SERVICE
。這與使用 GET_LOCK()
函式取得的鎖定不同,後者的 OBJECT_TYPE
為 USER LEVEL LOCK
。
鎖定命名空間、名稱和模式會顯示在 OBJECT_SCHEMA
、OBJECT_NAME
和 LOCK_TYPE
欄中。讀取和寫入鎖定的 LOCK_TYPE
值分別為 SHARED
和 EXCLUSIVE
。
LOCK_STATUS
值對於已取得的鎖定為 GRANTED
,對於正在等待的鎖定為 PENDING
。如果一個連線階段持有寫入鎖定,而另一個連線階段正在嘗試取得具有相同識別碼的鎖定,則您應該會看到 PENDING
。
鎖定服務的 SQL 介面實作了本節中描述的可載入函式。如需使用範例,請參閱 使用鎖定服務函式介面。
函式共用這些特性
傳回值在成功時為非零值。否則,會發生錯誤。
命名空間和鎖定名稱必須是非
NULL
、非空,且最大長度為 64 個字元。逾時值必須是整數,表示在放棄並出現錯誤之前,等待取得鎖定的秒數。如果逾時值為 0,則不會等待,且如果無法立即取得鎖定,則函式會產生錯誤。
這些鎖定服務函式可供使用
service_get_read_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)使用給定的鎖定名稱,在給定的命名空間中取得一個或多個讀取(共用)鎖定,如果未在給定的逾時值內取得鎖定,則會逾時並出現錯誤。
service_get_write_locks(
namespace
,lock_name
[,lock_name
] ...,timeout
)使用給定的鎖定名稱,在給定的命名空間中取得一個或多個寫入(獨佔)鎖定,如果未在給定的逾時值內取得鎖定,則會逾時並出現錯誤。
service_release_locks(
namespace
)針對指定的命名空間,釋放目前會期中使用
service_get_read_locks()
和service_get_write_locks()
取得的所有鎖定。如果命名空間中沒有鎖定,這並非錯誤。