二進制日誌包含有關修改資料庫內容的 SQL 語句資訊。此資訊以描述修改的「「事件」」形式儲存。(二進制日誌事件與排程事件儲存物件不同。)二進制日誌有兩個重要目的:
對於複寫,二進制日誌在來源複寫伺服器上用作發送到副本伺服器的語句記錄。來源將其二進制日誌中包含的事件發送到其副本,這些副本執行這些事件以進行與來源上相同的資料變更。請參閱 第 19.2 節「複寫實作」。
某些資料復原操作需要使用二進制日誌。還原備份檔案後,將重新執行備份後記錄的二進制日誌中的事件。這些事件使資料庫從備份點恢復為最新狀態。請參閱 第 9.3.2 節「使用備份進行復原」。
但是,如果日誌記錄在語句層級發生,則在儲存程式(儲存程序和函數、觸發器和事件)方面存在某些二進制日誌記錄問題:
在某些情況下,語句可能會影響來源和副本上不同的行集合。
在副本上執行的複寫語句由副本的應用程式執行緒處理。除非您實作複寫權限檢查(請參閱 第 19.3.3 節「複寫權限檢查」),否則應用程式執行緒具有完全權限。在這種情況下,程序可能會在來源和副本伺服器上遵循不同的執行路徑,因此使用者可以編寫包含僅在副本上執行的危險語句的常式。
如果修改資料的儲存程式是不確定的,則它是不可重複的。這可能會導致來源和副本上的資料不同,或導致還原的資料與原始資料不同。
本節說明 MySQL 如何處理儲存程式的二進制日誌記錄。它說明實作對儲存程式的使用所施加的目前條件,以及您可以採取哪些措施來避免日誌記錄問題。它還提供有關這些條件原因的其他資訊。
除非另有說明,此處的說明假設伺服器上已啟用二進制日誌記錄(請參閱 第 7.4.4 節「二進制日誌」)。如果未啟用二進制日誌,則無法進行複寫,並且二進制日誌也無法用於資料復原。預設情況下啟用二進制日誌記錄,只有在啟動伺服器時使用 --skip-log-bin
或 --disable-log-bin
才會停用。
一般而言,此處描述的問題會在 SQL 語句層級發生二進制日誌記錄時(基於語句的二進制日誌記錄)發生。如果您使用基於列的二進制日誌記錄,則日誌會包含因執行 SQL 語句而對個別列所做的變更。當常式或觸發器執行時,會記錄列變更,而不是進行變更的語句。對於儲存程序,這表示不會記錄 CALL
語句。對於儲存函數,會記錄函數內所做的列變更,而不是函數調用。對於觸發器,會記錄觸發器所做的列變更。在副本端,只會看到列變更,而不會看到儲存程式調用。
混合格式二進制日誌記錄(binlog_format=MIXED
)使用基於語句的二進制日誌記錄,除非僅基於列的二進制日誌記錄才能保證產生正確的結果。對於混合格式,當儲存函數、儲存程序、觸發器、事件或預備語句包含任何對於基於語句的二進制日誌記錄不安全的内容時,整個語句會被標示為不安全並以列格式記錄。用於建立和刪除程序、函數、觸發器和事件的語句始終是安全的,並以語句格式記錄。有關基於列、混合和基於語句的日誌記錄,以及如何判斷安全和不安全的語句的詳細資訊,請參閱 第 19.2.1 節「複寫格式」。
MySQL 中使用儲存函數的條件可以總結如下。這些條件不適用於儲存程序或事件排程器事件,並且除非啟用二進制日誌記錄,否則這些條件不適用。
若要建立或變更儲存函數,除了通常需要的
CREATE ROUTINE
或ALTER ROUTINE
權限之外,您還必須具有SET_ANY_DEFINER
權限。(根據函數定義中的DEFINER
值,無論是否啟用二進制日誌記錄,都可能需要SET_ANY_DEFINER
。請參閱 第 15.1.17 節「CREATE PROCEDURE 和 CREATE FUNCTION 語法」。)當您建立儲存函數時,您必須宣告該函數是確定的,還是不修改資料。否則,它對於資料復原或複寫可能是不安全的。
預設情況下,若要接受
CREATE FUNCTION
語句,則必須明確指定DETERMINISTIC
、NO SQL
或READS SQL DATA
中的至少一個。否則會發生錯誤。ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
此函數是確定的(並且不修改資料),因此是安全的。
CREATE FUNCTION f1(i INT) RETURNS INT DETERMINISTIC READS SQL DATA BEGIN RETURN i; END;
此函數使用
UUID()
,它不是確定的,因此該函數也不是確定的且不安全。CREATE FUNCTION f2() RETURNS CHAR(36) CHARACTER SET utf8mb4 BEGIN RETURN UUID(); END;
此函數會修改資料,因此可能不安全。
CREATE FUNCTION f3(p_id INT) RETURNS INT BEGIN UPDATE t SET modtime = NOW() WHERE id = p_id; RETURN ROW_COUNT(); END;
對函數性質的評估是基於建立者的「「誠實」」。MySQL 不會檢查宣告為
DETERMINISTIC
的函數是否沒有產生不確定結果的語句。當您嘗試執行儲存函數時,如果設定
binlog_format=STATEMENT
,則必須在函數定義中指定DETERMINISTIC
關鍵字。如果不是這種情況,則會產生錯誤,並且該函數不會執行,除非指定log_bin_trust_function_creators=1
來覆寫此檢查(請參閱下文)。對於遞迴函數調用,DETERMINISTIC
關鍵字僅在最外層調用時需要。如果正在使用基於列的或混合的二進制日誌記錄,即使函數的定義中沒有DETERMINISTIC
關鍵字,也會接受並複製該語句。由於 MySQL 不會在建立時檢查函數是否真的是確定的,因此調用具有
DETERMINISTIC
關鍵字的儲存函數可能會執行對於基於語句的日誌記錄不安全的動作,或者調用包含不安全語句的函數或程序。如果設定binlog_format=STATEMENT
時發生這種情況,則會發出警告訊息。如果正在使用基於列的或混合的二進制日誌記錄,則不會發出警告,並且該語句會以基於列的格式複製。若要放寬先前對函式建立的條件(您必須擁有
SUPER
權限,且函式必須宣告為確定性的或不修改資料),請將全域log_bin_trust_function_creators
系統變數設為 1。預設情況下,此變數的值為 0,但您可以像這樣變更它mysql> SET GLOBAL log_bin_trust_function_creators = 1;
您也可以在伺服器啟動時設定此變數。
如果未啟用二進制日誌,則
log_bin_trust_function_creators
不適用。除非如先前所述,函式定義中的DEFINER
值需要,否則建立函式不需要SUPER
權限。有關可能不安全的內建函式,會影響複寫(因此也會導致使用它們的預存函式不安全),請參閱 第 19.5.1 節「複寫功能和問題」。
觸發程序與預存函式類似,因此先前關於函式的評論也適用於觸發程序,但以下例外:CREATE TRIGGER
沒有可選的 DETERMINISTIC
特性,因此觸發程序會被假設為始終是確定性的。但是,在某些情況下,此假設可能無效。例如,UUID()
函式是不確定的(且不會複寫)。在觸發程序中使用此類函式時請小心。
觸發程序可以更新資料表,因此如果您沒有所需的權限,使用 CREATE TRIGGER
時,會發生與預存函式類似的錯誤訊息。在複本端,複本使用觸發程序的 DEFINER
屬性來判斷哪個使用者被視為觸發程序的建立者。
本節的其餘部分提供了有關日誌實作及其含義的更多詳細資訊。除非您對目前預存常式使用中,與日誌相關條件的基本原理感興趣,否則您無需閱讀它。此討論僅適用於基於語句的日誌,而不適用於基於列的日誌,但第一項除外:無論日誌模式為何,CREATE
和 DROP
語句都會記錄為語句。
伺服器會將
CREATE EVENT
、CREATE PROCEDURE
、CREATE FUNCTION
、ALTER EVENT
、ALTER PROCEDURE
、ALTER FUNCTION
、DROP EVENT
、DROP PROCEDURE
和DROP FUNCTION
語句寫入二進制日誌。如果函式變更資料且發生在否則不會記錄的語句中,則會將預存函式調用記錄為
SELECT
語句。這可防止非記錄語句中使用預存函式所導致的資料變更不複寫。例如,SELECT
語句不會寫入二進制日誌,但SELECT
可能會調用會進行變更的預存函式。為了處理此情況,當給定函式進行變更時,會將SELECT
語句寫入二進制日誌。假設在來源伺服器上執行下列語句func_name
()CREATE FUNCTION f1(a INT) RETURNS INT BEGIN IF (a < 3) THEN INSERT INTO t2 VALUES (a); END IF; RETURN 0; END; CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (1),(2),(3); SELECT f1(a) FROM t1;
當執行
SELECT
語句時,會調用函式f1()
三次。其中兩個調用會插入一列,而 MySQL 會為每個調用記錄一個SELECT
語句。也就是說,MySQL 會將下列語句寫入二進制日誌SELECT f1(1); SELECT f1(2);
當函式調用導致錯誤的預存程序時,伺服器也會記錄預存函式調用的
SELECT
語句。在這種情況下,伺服器會將SELECT
語句連同預期的錯誤代碼寫入日誌。在複本上,如果發生相同的錯誤,這是預期的結果,並且複寫會繼續。否則,複寫會停止。記錄預存函式調用而不是函式執行的語句,對複寫具有安全性隱含意義,其來自兩個因素
函式有可能在來源和複本伺服器上遵循不同的執行路徑。
在複本上執行的語句由複本的應用程式執行緒處理。除非您實作複寫權限檢查(請參閱 第 19.3.3 節「複寫權限檢查」),否則應用程式執行緒具有完整權限。
隱含意義是,雖然使用者必須具有
CREATE ROUTINE
權限才能建立函式,但使用者可以編寫一個包含危險語句的函式,該語句僅在複本上執行,並由具有完整權限的執行緒處理。例如,如果來源和複本伺服器的伺服器 ID 值分別為 1 和 2,則來源伺服器上的使用者可以如下建立並調用不安全的函式unsafe_func()
mysql> delimiter // mysql> CREATE FUNCTION unsafe_func () RETURNS INT -> BEGIN -> IF @@server_id=2 THEN dangerous_statement; END IF; -> RETURN 1; -> END; -> // mysql> delimiter ; mysql> INSERT INTO t VALUES(unsafe_func());
CREATE FUNCTION
和INSERT
語句會寫入二進制日誌,因此複本會執行它們。由於複本的應用程式執行緒具有完整權限,因此它會執行危險語句。因此,函式調用在來源和複本上具有不同的影響,並且不具有複寫安全性。為了保護啟用二進制日誌的伺服器免受此危險,預存函式建立者除了所需的常用
CREATE ROUTINE
權限外,還必須具有SUPER
權限。同樣地,若要使用ALTER FUNCTION
,您除了ALTER ROUTINE
權限外,還必須具有SUPER
權限。如果沒有SUPER
權限,則會發生錯誤ERROR 1419 (HY000): You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
如果您不希望要求函式建立者具有
SUPER
權限(例如,如果您的系統上具有CREATE ROUTINE
權限的所有使用者都是經驗豐富的應用程式開發人員),請將全域log_bin_trust_function_creators
系統變數設為 1。您也可以在伺服器啟動時設定此變數。如果未啟用二進制日誌,則log_bin_trust_function_creators
不適用。除非如先前所述,函式定義中的DEFINER
值需要,否則建立函式不需要SUPER
權限。建議使用複寫權限檢查,無論您對函式建立者的權限做出何種選擇。可以設定複寫權限檢查,以確保僅授權複寫通道執行預期且相關的操作。如需執行此操作的指示,請參閱 第 19.3.3 節「複寫權限檢查」。
如果執行更新的函式是不確定的,則它無法重複執行。這可能會產生兩個不良影響
它會導致複本與來源不同。
還原的資料與原始資料不符。
為了處理這些問題,MySQL 強制執行以下要求:在來源伺服器上,除非您宣告該函式是確定性的或不修改資料,否則會拒絕建立和變更函式。這裡適用兩組函式特性
DETERMINISTIC
和NOT DETERMINISTIC
特性指示函式對於給定的輸入是否總是產生相同的結果。如果未給定這兩種特性,則預設值為NOT DETERMINISTIC
。若要宣告函式是確定性的,您必須明確指定DETERMINISTIC
。CONTAINS SQL
、NO SQL
、READS SQL DATA
和MODIFIES SQL DATA
特性提供有關函式是否讀取或寫入資料的資訊。NO SQL
或READS SQL DATA
都表示函式不會變更資料,但您必須明確指定其中一個,因為如果未給定特性,預設值為CONTAINS SQL
。
預設情況下,若要接受
CREATE FUNCTION
語句,則必須明確指定DETERMINISTIC
、NO SQL
或READS SQL DATA
中的至少一個。否則會發生錯誤。ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
如果您將
log_bin_trust_function_creators
設定為 1,則會捨棄函式必須是確定性或不修改資料的要求。預存程序呼叫是在語句層級而非
CALL
層級記錄。也就是說,伺服器不會記錄CALL
語句,它會記錄實際執行的程序中的那些語句。因此,在來源伺服器上發生的相同變更也會在複本上發生。這可防止程序在不同機器上具有不同執行路徑可能導致的問題。一般而言,預存程序中執行的語句會使用與獨立執行語句相同的規則寫入二進制日誌。記錄程序語句時會特別小心,因為程序中的語句執行與非程序內容中的語句執行不太相同
要記錄的語句可能包含對本機程序變數的參照。這些變數不存在於預存程序內容之外,因此無法逐字記錄參照此類變數的語句。相反地,為了記錄目的,每個對本機變數的參照都會被此結構取代
NAME_CONST(var_name, var_value)
var_name
是本機變數名稱,而var_value
是常數,表示變數在記錄語句時的值。NAME_CONST()
的值為var_value
,而 「name」 為var_name
。因此,如果您直接調用此函式,您會得到如下的結果mysql> SELECT NAME_CONST('myname', 14); +--------+ | myname | +--------+ | 14 | +--------+
NAME_CONST()
使記錄的獨立語句能夠在複本上執行,其效果與在預存程序中來源上執行的原始語句相同。當來源資料行的運算式參考到本機變數時,使用
NAME_CONST()
可能會導致CREATE TABLE ... SELECT
陳述式出現問題。將這些參考轉換為NAME_CONST()
運算式可能會導致來源和複本伺服器上的資料行名稱不同,或者名稱過長而無法成為合法的資料行識別碼。一種解決方法是為參考本機變數的資料行提供別名。當myvar
的值為 1 時,請考慮以下陳述式:CREATE TABLE t1 SELECT myvar;
此陳述式會被改寫如下:
CREATE TABLE t1 SELECT NAME_CONST(myvar, 1);
為了確保來源和複本資料表具有相同的資料行名稱,請像這樣撰寫陳述式:
CREATE TABLE t1 SELECT myvar AS myvar;
改寫後的陳述式變成:
CREATE TABLE t1 SELECT NAME_CONST(myvar, 1) AS myvar;
要記錄的陳述式可能包含對使用者定義變數的參考。為了處理這個問題,MySQL 會將
SET
陳述式寫入二進位記錄檔,以確保複本上的變數存在,且其值與來源上的相同。例如,如果陳述式參考變數@my_var
,則該陳述式在二進位記錄檔中會以以下陳述式為前導,其中value
是來源上@my_var
的值:SET @my_var = value;
程序呼叫可能會發生在已提交或已回滾的交易中。會考慮交易上下文,以便正確複寫程序執行的交易層面。也就是說,伺服器會記錄程序中實際執行並修改資料的陳述式,並根據需要記錄
BEGIN
、COMMIT
和ROLLBACK
陳述式。例如,如果程序只更新交易資料表,並且在回滾的交易中執行,則不會記錄這些更新。如果程序發生在已提交的交易中,則會使用更新記錄BEGIN
和COMMIT
陳述式。對於在回滾交易中執行的程序,其陳述式會使用與獨立執行時相同的規則進行記錄。
如果程序是從儲存函數內調用的,則不會在陳述式層級將儲存程序呼叫寫入二進位記錄檔。在這種情況下,唯一記錄的內容是調用函數的陳述式(如果它發生在被記錄的陳述式中)或
DO
陳述式(如果它發生在未記錄的陳述式中)。因此,即使程序本身是安全的,也應該謹慎使用調用程序的儲存函數。