2010-02-13 47 views
1

這些是那些我遇到: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

首先,抱歉在這篇文章無恥的插頭數量。我這樣做是因爲我已經與許多這些問題鬥爭過,並且詳細解釋它們會花費太多時間。有利的一面是,我鏈接的所有文章都應該通過檢查第一段的內容給你一個清晰的概念,看看它們是否值得閱讀。

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的另一個限制是您不能在存儲的func中使用它工作,或觸發

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語句,分號之一。這意味着,要確定你必須先設置分隔符到別的一個存儲程序,因此分號可以用來separatee存儲程序語句:

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 < 5.5的功能,請看這裏:http://rpbouman.blogspot.com/2005/11/using-udf-to-raise-errors-from-inside.html。還有其他方法,但他們都依靠提高其他已知的但不是用戶定義的錯誤。

性能

我想其他的重要疑難雜症是MySQL存儲過程並不快。在普通SQL中執行表達式的速度要比將它們放入存儲函數或存儲過程時要快得多。存儲例程中的SQL大約和SQL一樣快,但是你不會從預編譯中受益,因爲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

循環遊標時,不能更新遊標內聯 - 它是隻讀的,只能轉發。另一個'gotcha'並不是一個棘手的問題,但值得一提的是:你只能使用標準的ansi sql來控制遊標循環。該語法往往比MS SQL和Oracle(也可能是其他產品)中的等效語法冗長得多。對於光標在MySQL循環看到http://rpbouman.blogspot.com/2005/09/want-to-write-cursor-loop-with-mysql.html

參數

您不能指定存儲程序參數的默認值。

沒有模塊/ PACKAGE

存儲程序是 - 該容器是數據庫。您不能在包或模塊中打包多個相關的例程。

這些是我現在想到的最不可思議的事情。我相信你可以考慮更多的事情。

+0

>不正確。局部變量對此很好。在OP中,我說* global *變量。檢查此:http://bugs.mysql.com/bug.php?id=2261 –

+0

我認爲'全球'你的意思是'用戶定義的變量'? 好的。對不起,我誤解了。這就是說,你爲什麼要這麼做?如果這是一個食譜,它將是一個意大利麪條。正確的做法是使用OUT參數並將這些OUT參數的值分配給用戶定義的變量。 –

0

存儲過程的參數

如果你是一個ASP.NET開發人員喜歡我,你,如果你不小心在以相同的順序指定你的參數得到一些莫名其妙的錯誤WTF他們出現在你的MySQL程序中。 (I blogged about it here

另外,如果你是一個ASP.NET開發人員,你會驚訝地發現輸入變量沒有像MSSQL那樣以@作爲前綴 - 因爲MySQL中的@表示一個用戶/會話變量。

存儲選擇結果到一個變量

它比MSSQL不同,語法是相當困難的在互聯網上找到。你必須做的只有一條路,像這樣:

SELECT MyTextColumn INTO myVariable FROM myTable WHERE ... ; 

PRINT

不自動推斷出它需要轉換它的arguement變量轉換爲字符串類型,相反,它只是拋出你一個錯誤。

CURRENT_TIMESTAMP

如果你想在日期/時間自動更新列,你只能用TIMESTAMP類型與CURRENT_TIMESTAMP默認列值做到這一點。這裏的疑問是DATETIME沒有像這樣的默認列值函數。而且,每個表只能有一個CURRENT_TIMESTAMP默認值列。

數據類型

,可以寫一個關於疑難雜症的MySQL中的數據類型小說,但這裏有一些最常見的:

  • VARCHAR只存儲最多255個字符。 TEXT用於更多。
  • 日期的信息被存儲像YYYY-MM-DD(相對於MS的MM-DD-YYYY
  • 沒有布爾數據類型; BIT用於存儲數據,如0100101
  • VARCHAR(8)不指示文本值最多8個字符但最多8個字節的字段。
  • INTEGER(8)以同樣的方式工作。