二進位日誌包含關於修改資料庫內容的 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
陳述式(如果它發生在未記錄的陳述式中)。因此,即使程序本身是安全的,也應謹慎使用調用程序的預存函數。