Mysql存储例程 - 常见陷阱?

时间:2010-02-13 13:23:46

标签: mysql

这些是我遇到过的:

  • 无法使用ALTER PROCEDURE更改存储过程的主体。应使用DROP PROCEDURE和CREATE PROCEDURE。

  • PREPARE不接受本地变量。这不起作用:
    DECLARE sql VARCHAR(32) DEFAULT 'SELECT 1';
    PREPARE stmt FROM sql;

  • FETCH [cursor_name] INTO ..不接受全局变量。这不起作用:

    FETCH mycursor INTO @a;

还有更多例子吗?

2 个答案:

答案 0 :(得分:2)

首先,对于这篇文章中无耻插头的数量感到抱歉。我这样做是因为我遇到了很多这些问题,并且每一个细节都要详细解释它们需要花费太多时间。从好的方面来说,我链接到的所有文章都应该通过查看第一段左右来清楚地了解它们是否值得阅读。

  

FETCH [cursor_name] INTO ..不接受全局变量。

不正确。局部变量就可以正常工作。

<强> PREPARE

至于PREPARE,与你提到的一样,为占位符指定参数值也需要使用用户定义的变量,而不是局部变量:

PREPARE stmt FROM 'INSERT INTO tab VALUES (?,?)';
EXECUTE stmt USING @val1, @val2;
DEALLOCATE PREPARE stmt;

有关PREPARE的更多信息,请参阅我的无耻插件:http://rpbouman.blogspot.com/2005/11/mysql-5-prepared-statement-syntax-and.html

PREPARE的另一个限制是您无法在存储的函数或触发器中使用它

<强> TRIGGERS

目前,MySQL只实现了FOR EACH ROW个触发器。这意味着您只能对行级INSERT,UPDATE或DELETE事件执行操作。您可以使用一种技巧来模拟BEFORE STATEMENT触发器,请参阅:http://rpbouman.blogspot.com/2006/04/mysql-hack-emulates-before-statement_30.html。然而,这并不是真正有用 - 更有用的是AFTER STATEMENT触发器,但我没有找到任何方法来实现这一点。

触发器的另一个限制是每个表只能有一种类型。触发器的“类型”由四个部分组成:触发时间(前/后),触发器语句(INSERT,UPDATE,DELETE),触发器级别(语句/行,其中仅实现ROW)。因此,例如在mysql中,您在任何给定的表上只能有一个BEFORE INSERT FOR EACH ROW触发器。

触发器的另一个非常重要的限制是它们不会触发CASCADE-ing外键导致的操作。因此,对于innodb表上的外键的级联操作而导致的UPDATE和DELETE不会在受级联操作影响的表上触发任何UPDATE / DELETE触发器。

<强> DELIMITER

我认为最常见的问题是存储例程中的语句分隔符与用于分隔普通sql语句的分号相同,即分号。这意味着要定义存储的例程,您必须首先将分隔符设置为其他内容,以便分号可用于分隔存储的例程语句:

DELIMITER $$

CREATE PROCEDURE p(p_name)
BEGIN
    INSERT INTO tab VALUES (p_name);  -- these statements are terminated with semi-colon
    SELECT last_insert_id();
END; -- after this we do the custom delimiter to create the procedure
$$ 

-- now let's reset the delimiter again:
DELIMITER ; 

CALL p('Boe'); -- we can use the semi-colon again now.

有关MySQL分隔符的更多信息,请参阅:http://rpbouman.blogspot.com/2008/02/most-misunderstood-character-in-mysqls.html

引发错误

一个非常重要的问题是你不能在MySQL 5.0 ... 5.1中引发用户定义的错误。有关问题的说明,请参阅http://rpbouman.blogspot.com/2006/02/dont-you-need-proper-error-handling.html。在MySQL 5.5中修复了此问题,您可以使用标准SQL SIGNAL语句来执行此操作。见http://rpbouman.blogspot.com/2009/12/validating-mysql-data-entry-with_15.html。对于解决MySQL v&lt;这个缺乏功能的解决方案来说5.5看这里:http://rpbouman.blogspot.com/2005/11/using-udf-to-raise-errors-from-inside.html。还有其他方法,但它们都依赖于提出一些其他已知但非用户定义的错误。

<强>性能

我想另一个重要的问题是MySQL存储过程不快。普通SQL中的表达式执行速度比将它们放入存储函数或存储过程中要快得多。存储例程中的SQL与SQL plain一样快,但是你没有从预编译中受益,因为MySQL没有实现它。一些简单的基准测试说明了这一点:

mysql> SELECT BENCHMARK(10000000, 1+1);
+--------------------------+
| benchmark(10000000, 1+1) |
+--------------------------+
|                        0 |
+--------------------------+
1 row in set (0.30 sec)

现在函数中的等价物:

mysql> CREATE FUNCTION f_one_plus_one() RETURNS INT RETURN 1+1;
mysql> SELECT BENCHMARK(10000000, f_one_plus_one());
+---------------------------------------+
| benchmark(10000000, f_one_plus_one()) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (28.73 sec)

现在你可以快速地将函数的性能提高到28.73 / 0.30,这大约慢了100倍,因为还有一些其他因素需要考虑。我的建议是对您的特定代码进行基准测试。

<强> CURSORS

另一个问题是游标。它们很慢,因为它们在打开它们时会在临时表中实现。要了解游标性能与纯SQL的比较,请参阅http://rpbouman.blogspot.com/2006/09/refactoring-mysql-cursors.html

循环游标时,无法内联更新游标 - 它是只读的,只能转发。另一个'问题'并不是真正的问题,但值得一提的是:你只能使用标准的ansi sql来控制游标循环。该语法往往比MS SQL和Oracle(以及其他产品)中的等效语法更加冗长。对于MySQL中的游标循环,请参阅http://rpbouman.blogspot.com/2005/09/want-to-write-cursor-loop-with-mysql.html

<强>参数

您无法为存储的例程参数指定默认值。

没有模块/包装

存储例程就是这样 - 容器就是数据库。您无法在包或模块中打包多个相关例程。

这些是我此刻想到的最不受欢迎的事情。我相信你可以考虑更多的事情。

答案 1 :(得分:0)

存储过程参数

如果您是像我这样的ASP.NET开发人员,如果您不小心按照它们在MySQL过程中出现的完全相同的顺序指定您的参数,您将会遇到一些令人困惑的WTF错误。 (I blogged about it here

另外,如果您是ASP.NET开发人员,您会惊讶地发现输入变量不再像@在MSSQL中那样以@为前缀 - 因为MySQL中的@表示用户/会话变量。

将SELECT结果存储到变量

它与MSSQL不同,语法在互联网上很难找到。你只有一种方法可以做到这一点:

SELECT MyTextColumn INTO myVariable FROM myTable WHERE ... ;

打印

不会自动推断它需要将它的争论变量转换为字符串类型,而只会引发错误。

<强> CURRENT_TIMESTAMP

如果您想要一个自动更新日期/时间的列,则只能使用TIMESTAMP类型,默认列值为CURRENT_TIMESTAMP。这里的问题是DATETIME没有像这样的默认列值函数。并且,每个表只能有一个CURRENT_TIMESTAMP默认值列。

<强>数据类型

有人可以写一篇关于MySQL数据类型中的问题的小说,但这里有一些最常见的小说:

  • VARCHAR最多只能存储255个字符。 TEXT用于其他任何目的。
  • 日期信息的存储方式与YYYY-MM-DD相同(与MS的MM-DD-YYYY相对)
  • 没有BOOLEAN数据类型; BIT用于存储0100101
  • 等数据
  • VARCHAR(8)不表示文本值最多为8个字符但最多8个字节的字段。
  • INTEGER(8)的工作方式相同。