天天看點

mysql function 1418,mysql 建立函數導緻1418錯誤的解決辦法

該篇文章來源于線上案例結合官方文檔翻譯和自己了解。

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)

這個函數定義中沒有包含deterministic,no sql或者read sql data中的一種,并且binlog是開啟的。

很顯然是業務建立函數的時候觸發了該錯誤,那為何會導緻這種錯誤呢,這是由于mysql複制要保證主從資料的一緻決定的,這些函數建立語句會被記錄在binlog中,然後複制到slave執行。但是這些函數在slave上執行的時候有可能導緻主從資料不一緻,為了避免該問題,mysql拒絕直接建立函數,建立函數失敗,傳回異常。那為何mysql會有這個機制,對于函數的建立又将如何處理?

在一些場景下,一條語句在分别在主從執行可能導緻不同的結果,在slave執行複制語句是通過sql線程執行的,而SQL線程是有所有的權限,是以有可能出現一種情況就是,函數中有危險語句在master上執行并不會有問題,但是當slave的SQL線程權限不同的時候,就會執行到這些危險語句,進而導緻主從資料不一緻。如果一個函數更改資料的結果是不确定的,或者是不可重複的,也會導緻主從資料不一緻,或者導緻更改的資料和原始資料不一緻(這主要在備份恢複中出現)。

通常這些問題出現在複制是語句模式的情況下,如果使用的行模式,binlog記錄的是執行SQL語句影響到的具體的行(不是執行的SQL語句),當routines或者觸發器執行,binlog中記錄的也是更改行資訊,而不是影響行變更的SQL語句,對于存儲過程也是一樣,并不是記錄call 語句,也是記錄更改的行記錄。對于函數,日志記錄的是函數更改的行記錄,而不是函數調用語句。對于觸發器,記錄的是觸發器更改之後的行記錄。是以在slave這邊,看的是變更之後的行記錄,而不是這些子程式的調用語句。是以在行模式則不會導緻主從不一緻。

如果複制模式是混合模式,除非行模式能保證正确的結果,不然上面的結果記錄到binlog采用的是語句模式。在混合模式下,當一個存儲過程,函數,觸發器,事件包含了對于語句模式不安全的SQL,這些語句就會标記為不安全的并且采用行模式記錄在binlog。

在mysql中,下面有一些條件是對函數有效,對存儲過程或者事件無效,或者沒有開啟binlog也是無效的:

1.建立或者更改一個函數必須要有super權限

2.建立一個函數,必須要定義為确定結果的或者是不更改資料的。否則,就會被認為是對複制或者資料恢複是不安全的,也就是報錯1418

預設情況下,要建立一個函數,deterministic,no sql,reads sql data中三個屬性中的一個必須被顯示指定,這樣就能确認函數對結果集的影響,否則就會報錯1418,函數建立不成功。

下面這個函數就是确定結果的,是以是可以建立成功的:

CREATE FUNCTION f1(i INT)

RETURNS INT

DETERMINISTIC

READS SQL DATA

BEGIN

RETURN i;

END;

下面函數使用了uuid(),這個函數的結果是不确定的,是以下面函數是非确定結果的,是複制不安全的,是以建立失敗:

CREATE FUNCTION f2()

RETURNS CHAR(36) CHARACTER SET utf8

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并不會檢查一個函數定義為确定結果但實際上産生了不确定的結果。在函數的定義中可以指定deteministic來顯示的說明函數是安全的,但是在函數體中定義可以使用不安全的語句。

這種情況,mysql會認為是安全的,可以建立函數,但實際上這種函數調用對主從資料可能導緻不一緻。

如果試圖執行一個函數,若binlog_format 設定為statement模式,這個函數屬性必須要顯示指定為deterministic才行執行,否則就會報錯1418異常并且函數不會被執行。但是設定了log_bin_trust_function_creators = 1,則可以正常執行。

比如:

先set global log_bin_trust_function_creators =1 建立了一個沒有指定deterministic的函數:

CREATE FUNCTION f2()

RETURNS CHAR(36) CHARACTER SET utf8

BEGIN

RETURN UUID();

END;

再set globallog_bin_trust_function_creators  = 0;set binlog_format = statement;

然後調用該函數

mysql>  select f2();

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)

調用函數失敗,報錯1418。

如果設定binlog_format=mixed 或者row 模式或者set globallog_bin_trust_function_creators  = 1 ,select f2()則可以正常執行了(建立函數的時候沒有指定determinisric 關鍵字),或者在函數定義中指定deterministic屬性,也是可以正常執行的。

因為mysql并沒有檢查一個函數在建立的時候是否是正是确定結果的,是以調用一個指定了deterministic關鍵字的函數在statement模式下可能帶來的結果是不安全的,因為這樣的函數中可能包含不安全的語句。在statement模式下,調用這種函數會觸發warning,如果是mixed或者row模式,不會有warning,函數中語句會以row模式進行複制。如下:

建立函數的時候明确指定deterministic屬性

CREATE FUNCTION f3(p_id INT)

RETURNS INT

deterministic

BEGIN

UPDATE t1 SET x=1 WHERE x = p_id;

RETURN ROW_COUNT();

END;

這個函數不管log_bin_trust_function_creators   設定為多少都能建立成功,是以指定的結果是确定的。

在format_format=statement模式下調用:

mysql> select f3(1)//

+-------+

| f3(1) |

+-------+

|     0 |

+-------+

1 row in set, 1 warning (0.00 sec)

mysql> show warnings//

+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| Level | Code | Message                                                                                                                                                                                                  |

+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| Note  | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statement is unsafe because it uses a system function that may return a different value on the slave. |

+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00 sec)

産生了warning,提示語句在statement模式下是不安全的(和log_bin_trust_function_creators 設定無關,設定1或者0,都會有warning産生)

在binlog_format = row模式下調用:

mysql> set binlog_format=row//

Query OK, 0 rows affected (0.00 sec)

mysql> select f3(1)//

+-------+

| f3(1) |

+-------+

|     0 |

+-------+

1 row in set (0.00 sec)

并不會提示warning,在mixed模式下也是一樣,不會有warning,都是以row模式進行binlog記錄。

可以看出,即使我們建立函數的時候繞過了mysql的檢查,成功建立了函數,但是在調用的時候,mysql還是會根據binlog_format來确認結果,進而選擇是row模式,還是statement,并作出提示。

為了避開建立函數的檢查條件,可以設定log_bin_trust_function_creators = 1,這樣mysql就不會進行檢查了,預設設定0,這個參數隻能設定global 級别。也可以在server啟動的時候加上--log_bin_trust_function_creators =1選項。如果binlog沒有開啟,log_bin_trust_function_creators 參數也就沒作用。

觸發器和函數類似,是以前面講的關于函數的說明同樣對觸發器有效,除了下面這個:create trigger語句沒有deterministic屬性,是以觸發器總是被假定為deterministic,但是在一些場景下這個假設會失效。例如,uuid()函數就是nondeterministic的(不能複制的),是以在使用這些函數尤其注意。觸發器會更新表,是以create trigger語句沒有更新表所需要的權限時,會和建立函數一樣報異常。在slave端,slave通過觸發器的definer屬性來決定觸發器的建立者,這就就決定了觸發器所需的權限。

如果一個函數更新了資料 ,那麼mysql會以select語句的形式記錄調用方式,這就避免了資料更新無法記錄日志進而使得無法複制。一般情況下,select語句是不會記錄子binlog中的,但是一個select語句有可能調用一個函數

導緻資料變更。為了解決這種方式,當select使用的函數更新了資料,那麼函數調用方式就以select語句的形式記錄在binlog中(這裡有個前提,binlog_format=statment。row和mixed不存在這個問題)

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;

在binlog中可以發現是以select語句記錄binlog的:

mysql function 1418,mysql 建立函數導緻1418錯誤的解決辦法

圖1

如果是row或者mixed模式,則都是以row模式進行binlog記錄。

當一個函數中調用一個存儲過程的時候發生了錯誤,這樣mysql同樣會以select語句的形式記錄函數的調用,這種情況下,binlog中記錄的select語句同時會記錄

期望的error code,在slave端,如果同樣的錯誤出現,那麼這就是期望的結果同時複制不會中斷,否則複制會中斷。

binlog記錄函數的調用方式而不是記錄函數中的執行語句對複制是一種安全的結果,主要有兩個方面的原因:

在master slave上函數的的調用路徑可能不同,其次執行語句的SQL 線程有所有權限,和master可能不同,master可能沒這麼大的權限,但是slave上有,就有可能導緻主從上執行結果不同。

這樣的結果就是雖然一個使用者需要create routine的權限來建立一個函數,使用者可以寫很危險的語句在函數中,而且隻能在slave上通過有所有權限的SQL 線程來執行。例如,master slave有不同的server id 1 和2,一個使用者可以在master上建立一個不安全的函數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());

函數建立語句和插入語句都會記錄在binlog中,是以slave可以執行這些語句,因為SQL 線程有所有權限(往往主庫建立和調用函數的使用者權限比較有限),是以将會執行到這些危險語句,是以,在master slave

上這個函數調用産生來不一樣的結果,是以它不是複制安全的。

為了避免開啟binlog的mysql上的這種危險情況,函數的建立這必須有所有權限,不僅僅是必須的create routine權限。同樣的,alter function也是一樣。沒有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權限,可以設定全局參數log_bin_trust_function_creators =1,或者在服務啟動的時候增加參數--log_bin_trust_function_creatros =1,如果binlog沒有開啟,這個參數則沒有作用。

如果一個函數更新資料是不确定的,是不可重複的,這會導緻兩個不良後果:

導緻slave和master的資料不一緻

恢複資料的時候導緻和原始資料不同(這裡主要是資料恢複時出現)

為了處理這些問題,mysql做如下強制要求:在master上,拒絕建立或者變更一個函數,除非定義的函數是确定結果的或者不更新資料的。這兩個函數屬性的作用如下:

deterministic 或者not deterministic屬性決定一個函數對于給定的輸入是否每次産生相同的結果,如果沒有屬性給定,預設是not deteministic的。定義一個函數是deterministic的,需要明确指定deterministic屬性。

contains sql ,no sql,reads sql data和modify sql data屬性指出一個函數是讀資料還是更新資料,no sql 或者reads sql data明确一個函數是不更新資料的。如果不指定屬性,預設是contains sql,如果一個函數是明确不更新資料的話,

需要特别指定no sql、reads sql data中的一種。預設情況下,要使得create function語句能否執行,至少需要deteministic ,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 ,則deterministic 或者不更新資料的屬性被忽略。

調用存儲過程,binlog記錄是在語句執行階段,而不是調用階段。也就是說binlog記錄的不是call 語句,而是存儲過程中真正執行的語句。是以在master更新的資料在slave上同樣被更新到,這就避免了同一個存儲過程在主從上導緻不一樣的結果。

通常,binlog中記錄的存儲過程中執行的語句隻要有同樣的權限就可以在另外一台機器上被執行。有一個特殊場景需要注意:在非标準的上下文中存儲過程的執行結果不完全相同:

一個語句記錄在binlog中可以要包含關聯一個本地的過程變量,這些變量在存儲過程外部是不存在的,是以一個引用了變量的語句不能直接按照語句的原樣進行記錄binlog,而是為了寫入binlog每一個被引用的變量用如下的結構進行替換:

NAME_CONST(var_name, var_value)

var_name 是本地的變量名,var_value是一個常量,表示引用這個變量的語句執行時候該變量的值,name_const()函數的值為var_value,名字為var_name.是以,如果直接調用該函數,可以得到如下結果:

mysql> SELECT NAME_CONST('myname', 14);

+--------+|myname|

+--------+|14|

+--------+

name_const()函數使得binlog中記錄在slave上執行和存儲過程中的原始語句在 master上執行産生同樣的效果。使用create table...select語句時候,當select 語句中當清單達式引用本地變量的時候,使用name_cons函數的時候會導緻一些問題。轉變這些引用為name_const表達式的過程中導緻master和slave為不同的列名,或者name太長也不能作為一個合法的列辨別。一種解決方式就是為列名提供一個别名引用本地變量。下面語句myvar 的值為1:

create table t1 select myvar;

記錄在binlog中會被重寫為如下語句:

create table t1 select name_const(myvar ,1);

為了確定master和slave有相同的列名,将語句寫成如下方式:

create table t1 select myvar as myvar;

binlog中會被記錄為如下:

create table t1 select name_const(myvar ,1) as myvar; 這樣就能確定主從的列名都是一緻的。

另外一條語句記錄到binlog中可能包含引用使用者定義的變量。為處理這個特點,mysql寫入一個set 語句到binlog確定該變量在slave上和master上有相同的值。例如,一條語句引用了變量@my_var,該語句在binlog中會處理為如下語句,value就是master上@my_var變量的值:

set @my_var = value;

存儲過程調用可以在包含commit或者rollback的事務中,事務的上下文會被記錄下來,這樣事務中的存儲過程部分就能夠在slave中正确的複制執行。也就是說,mysql記錄存儲過程中真實執行和更改資料的語句,同時必要的時候會記錄begin,commit,rollback。例如,一個存儲過程隻更新事務表而且在一個事務中執行,如果被復原了,那麼這部分更新不會被記錄在binlog中。如果一個存儲過程調用在一個commit的事務中,更新前後會記錄begin和commit語句。如果一個存儲過程調用在一個rollback的事務中,這些被記錄會以同樣的規則被記錄在binlog中,當其以獨立的方式執行時這些語句都會被應用:

事務表的更新不會記錄在binlog中

非事務表的更新會被記錄在binlog中,因為rollback不會復原掉非事務表的更新

如果同時更新事務表和非事務表,這些記錄會前後會記錄begin和rolleback語句,是以slave更新和復原的記錄就和master上更新和復原的記錄一緻。

在statement複制模式下,如果一個函數調用了一個存儲過程,binlog中不會記錄call語句。在這種場景下,隻有函數調用語句被記錄(如果調用它的語句被記錄在binlog)或者是一個do語句(如果調用它的資料沒有記錄),正因如此,在一個函數中調用存儲過程需要小心,盡管存儲過程自身是安全的。也就是說,在一個函數中調用存儲過程,binlog中隻會記錄函數的調用,不會記錄存儲過程的調用。

說了這麼多,就是為了說明mysql對于建立函數的一些限制,以及調用函數如何記錄binlog,以及我們在日常使用函數過程中,應該注意什麼。

再回到業務的報錯,建立函數失敗,函數中沒有指定deterministic ,no sql,reads sql data 屬性,進而導緻報錯,解決辦法如下:

1.如果函數隻是為了查詢友善而建立的,不更改資料,那麼可以指定reads sql data,deterministic屬性中的一種即可

2.如果函數要更改資料,那麼可以指定deterministic屬性通過建立函數,但是為了確定複制安全,需要将binlog_format設定為row模式