【问题标题】:CREATE FUNCTION error "This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA"CREATE FUNCTION 错误“此函数没有 DETERMINISTIC、NO SQL 或 READS SQL DATA”
【发布时间】:2011-06-22 15:59:36
【问题描述】:

我们的数据库具有生成订单号的功能。它从设置表中读取一个值,将其递增,然后返回新值。例如:

CREATE FUNCTION NextOrderNumber() RETURNS INTEGER UNSIGNED NOT DETERMINISTIC
BEGIN
  DECLARE number INTEGER UNSIGNED;
  UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1) WHERE KeyName='NextOrderNumber';
  SET number=LAST_INSERT_ID();
  return number;
END

注意:不要批评这个函数,我知道它有缺陷,它只是为了说明。

我们使用这个函数如下:

INSERT INTO Orders(OrderNumber, ...)
SELECT NextOrderNumber(), ...

启用二进制日志记录后,CREATE FUNCTION 会出现此错误:

这个函数没有 确定性、无 SQL 或读取 SQL DATA 在其声明和二进制文件中 日志记录已启用(您可能想要 使用不太安全的 log_bin_trust_function_creators 变量)

不管binlog_format设置什么,上面的功能真的有问题吗?根据我对相关MySQL page 的阅读,我看不出这个函数与复制不兼容的任何原因,无论是 ROW 还是 STATEMENT 级别的二进制日志记录。

如果函数是安全的,设置全局 log_bin_trust_function_creators=1 会让我感到不安。我不想为所有功能禁用此检查,仅此一项。我可以改为将函数标记为 NO SQL 以抑制警告吗?我试过了,它奏效了。这会引起什么问题吗?

【问题讨论】:

    标签: mysql stored-functions


    【解决方案1】:

    我已经用谷歌搜索了,我在这里。 我找到了一种方法:

    SET GLOBAL log_bin_trust_function_creators = 1;
    

    但要小心,数据恢复或复制可能不安全...

    【讨论】:

    • 这并没有以任何方式解决问题。
    【解决方案2】:

    据我了解,它在数据恢复或复制时会导致问题

    参考:http://dev.mysql.com/doc/refman/5.0/en/stored-programs-logging.html

    MySQL 5.0.6:记录创建存储例程和 CALL 语句的语句。存储函数调用在更新数据的语句中发生时会被记录(因为这些语句被记录)。

    但是,当函数调用发生在不更改数据的语句(例如 SELECT)中时,不会记录函数调用,即使数据更改发生在函数本身内也是如此;这可能会导致问题。

    在某些情况下,如果在不同时间或在不同(主从)机器上执行函数和过程可能会产生不同的效果,因此对于数据恢复或复制可能是不安全的。

    例如

    CREATE FUNCTION myfunc () RETURNS INT DETERMINISTIC
    BEGIN
      INSERT INTO t (i) VALUES(1);
      RETURN 0;
    END;
    
    SELECT myfunc();
    

    如果在不修改数据的语句(例如SELECT)中调用存储的函数,则函数的执行不会写入二进制日志,即使函数本身修改了数据。这种日志记录行为有可能导致问题。假设函数myfunc() 定义如上。

    【讨论】:

    • 我不认为这是正确的。查看link,您将看到斜体...对于存储函数,记录函数内所做的行更改,而不是函数调用。对于触发器,记录触发器所做的行更改。 ... 斜体 。所以对我来说,为什么 logbin_format=ROW 似乎仍然不允许未标记为 DETERMINISTIC 的函数是莫名其妙的。
    【解决方案3】:

    有两种方法可以解决这个问题:

    在 MySQL 控制台中执行以下命令:

    SET GLOBAL log_bin_trust_function_creators = 1;
    

    将以下内容添加到 mysql.ini 配置文件中:

    log_bin_trust_function_creators = 1
    

    该设置放宽了对非确定性函数的检查。非确定性函数是修改数据的函数(即具有更新、插入或删除语句)。有关详细信息,请参阅此处。

    请注意,如果未启用二进制日志记录,则此设置不适用。

    【讨论】:

    • 当我从视图中选择表格时,我遇到了这个错误。有什么解决办法?
    【解决方案4】:

    考虑一下写入二进制日志的内容。

    当事务在从属设备上播放时,您无法确保在主服务器上创建的订单会生成相同的序列 - 或者更有可能由集群中的另一个主服务器生成。例如

     0) Node 1 and Node 2 are in sync, NextOrderNumber=100
     1) Node 1 receives insert statement wrt order from customer A and assigns 
        order number 100, changes its NextOrderNumber to 101
     2) Node 1 writes the settings update to the log
     3) Node 1 writes the insert statement to the log
     4) Node 2 processes for customer B, asigns order number 100 and increments
     5) Node 2 writes the settings update from to the log
     6) Node 2 writes the insert statement to the log
     7) Nodes 2 reads settings update from the log @2 
             - Its NextOrderNumber is now 102
     8) Node 2 reads insert from log @3, tries to apply it but it fails 
             due to duplicate key
     9) Node 1 reads the update @5 - Its nextOrderNumber is also now 102
     10) Node1 reads insert from log @6 - 
             but this fails due to duplicate key
    

    现在2个节点上的order 100是指不同的数据,没有order 101。

    添加了很多功能来修改 auto_increment 变量的行为是有原因的。

    如果您将插入包装在一个过程中 - 它从序列生成器中检索一个值,然后将其嵌入到插入语句中,那么直接的问题将得到解决,但是您需要考虑如何避免使用不同的方法两次分配相同的数字数据库节点。

    【讨论】:

    • symcbean - 我知道这种方法在集群中不起作用。我不是在问那种情况。我问的是简单的主从设置,其中插入/更新只发生在一个地方。
    【解决方案5】:

    我可以将函数标记为 NO SQL 以抑制警告吗?我试过了,它奏效了。这会引起什么问题吗?

    据此Mysql doc

    对函数性质的评估基于创建者的“诚实”:MySQL 不会检查声明为 DETERMINISTIC 的函数是否没有产生不确定结果的语句。

    所以这取决于你。如果您确定该方法不会造成任何问题...

    【讨论】:

      【解决方案6】:

      编写属性对我有帮助。在此函数中,您需要编写 - MODIFIES SQL DATA - 因为该函数使用 UPDATE。如果函数中只使用 SELECT,那么我们将编写 READS SQL DATA。如果函数体中同时使用了数据读写操作符,也可以写这两个属性。

      CREATE FUNCTION NextOrderNumber() 
      RETURNS INTEGER 
      UNSIGNED 
      NOT DETERMINISTIC
      MODIFIES SQL DATA
      BEGIN
          DECLARE number INTEGER UNSIGNED;
          UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1) 
          WHERE KeyName='NextOrderNumber';
          SET number=LAST_INSERT_ID();
          return number;
      END
      

      【讨论】:

        【解决方案7】:

        在创建函数之前执行这个:

        SET @@global.log_bin_trust_function_creators = 1;
        

        并在声明中添加MODIFIES SQL DATA

        另外...好吧,您要求不要评论函数本身,但我建议您删除 number 变量并简单地执行 RETURN LAST_INSERT_ID()

        【讨论】:

        • 这不起作用。 log_bin_trust_function_creators 是全局变量,不是会话变量。
        • 嗯?全局行为不受影响??是的。您正在设置一个全局变量。
        【解决方案8】:

        添加READS SQL DATA 声明这是一个只读函数:

        CREATE FUNCTION NextOrderNumber() RETURNS INTEGER UNSIGNED NOT DETERMINISTIC
        READS SQL DATA
        BEGIN
          DECLARE number INTEGER UNSIGNED;
          UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1) WHERE KeyName='NextOrderNumber';
          SET number=LAST_INSERT_ID();
          return number;
        END
        

        【讨论】:

          猜你喜欢
          • 2014-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-20
          相关资源
          最近更新 更多