2012-07-06 62 views
0

我已經將我的問題簡化爲這個簡單的SP。列名在最後被緩存在SELECT *中。我不知道爲什麼或如何阻止它。我嘗試添加SQL_NO_CACHE,但這沒有什麼區別。如預期爲什麼mysql緩存被刪除臨時表的列名?

mysql> CALL mysp(0); 
+------+ 
| col1 | 
+------+ 
| 1 | 
| 3 | 
| 5 | 
+------+ 
3 rows in set (0.17 sec) 

+----------------------------------+ 
| Result       | 
+----------------------------------+ 
| Please see new temp table mydata | 
+----------------------------------+ 
1 row in set (0.17 sec) 

Query OK, 0 rows affected (0.17 sec) 

DROP TABLE IF EXISTS foo; 
CREATE TABLE foo(
col1 int, 
col2 int); 
INSERT INTO foo VALUES(1,2),(3,4),(5,6); 
DROP PROCEDURE IF EXISTS mysp; 
DELIMITER ;; 
CREATE [email protected] PROCEDURE mysp(c INT) 
BEGIN 
    DROP TABLE IF EXISTS mydata; 

    SET @mycol='col1'; 

    IF c > 0 THEN SET @mycol:='col2'; 
    END IF; 

    SET @s=CONCAT('CREATE TEMPORARY TABLE mydata AS SELECT ', @mycol, ' FROM foo'); 
    PREPARE stmt FROM @s; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt; 

-- The following select call fails on 2nd and subsequent executions of the SP 
    SELECT SQL_NO_CACHE * FROM mydata; 
    SELECT "Please see new temp table mydata" as Result; 
END ;; 
DELIMITER ; 

版本

mysql> SELECT VERSION(); 
+------------+ 
| VERSION() | 
+------------+ 
| 5.5.15-log | 
+------------+ 
1 row in set (0.00 sec) 

首先運行工作正常,現在,如果我嘗試運行它再次使用另一列

mysql> CALL mysp(1); 
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col1' in 'field list' 
mysql> SELECT @mycol; 
+--------+ 
| @mycol | 
+--------+ 
| col2 | 
+--------+ 
1 row in set (0.00 sec) 

如果我重新存儲過程再次它的窩RKS

mysql> CALL mysp(1); 
+------+ 
| col2 | 
+------+ 
| 2 | 
| 4 | 
| 6 | 
+------+ 
3 rows in set (0.18 sec) 

+----------------------------------+ 
| Result       | 
+----------------------------------+ 
| Please see new temp table mydata | 
+----------------------------------+ 
1 row in set (0.18 sec) 

Query OK, 0 rows affected (0.18 sec) 

但是,如果我試圖切換回第一列 - 即使我嘗試第一次投放的臨時表 - 它仍然無法正常工作要求通過eggyal

mysql> CALL mysp(0); 
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col2' in 'field list' 
mysql> DROP TABLE mydata; 
Query OK, 0 rows affected (0.03 sec) 

mysql> CALL mysp(0); 
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col2' in 'field list' 
mysql> 

* 其他信息。另外我在另一個mysql版本上嘗試了這個結果。 *

mysql> CALL mysp(1); 
+------+ 
| col2 | 
+------+ 
| 2 | 
| 4 | 
| 6 | 
+------+ 
3 rows in set (0.20 sec) 

+----------------------------------+ 
| Result       | 
+----------------------------------+ 
| Please see new temp table mydata | 
+----------------------------------+ 
1 row in set (0.20 sec) 

Query OK, 0 rows affected (0.20 sec) 

mysql> describe mydata; 
+-------+---------+------+-----+---------+-------+ 
| Field | Type | Null | Key | Default | Extra | 
+-------+---------+------+-----+---------+-------+ 
| col2 | int(11) | YES |  | NULL |  | 
+-------+---------+------+-----+---------+-------+ 
1 row in set (0.00 sec) 

mysql> CALL mysp(0); 
ERROR 1054 (42S22): Unknown column 'test.mydata.col2' in 'field list' 
mysql> describe mydata; 
+-------+---------+------+-----+---------+-------+ 
| Field | Type | Null | Key | Default | Extra | 
+-------+---------+------+-----+---------+-------+ 
| col1 | int(11) | YES |  | NULL |  | 
+-------+---------+------+-----+---------+-------+ 
1 row in set (0.00 sec) 

修復的有趣的發展 - 不斷變化的最後幾行到準備好的語句的工作 - 但正如前面使用完全相同的查詢。

-- The following select call fails on 2nd and subsequent executions of the SP 
    PREPARE stmt FROM 'SELECT SQL_NO_CACHE * FROM mydata'; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt; 
    SELECT "Please see new temp table mydata" as Result; 
+0

這個錯誤真的來自那個SP嗎?你可以在每次調用SP後顯示「mydata」的內容嗎? – eggyal 2012-07-06 22:58:10

+0

已添加。我開始懷疑MySQL客戶端緩存的東西。 – 2012-07-07 00:12:39

+1

好的,所以'*'在執行準備好的語句之前顯然會被擴展(可能是在調用SP之後)。我不認爲用'MODIFIES SQL DATA'特徵定義過程有什麼區別?事先沒有將'*'限定爲'mydata。*'或調用'FLUSH TABLES mydata'?如果沒有,你可以通過另一個準備好的語句(如@ spencer7593建議的)或者通過本程序調用的第二個過程執行'SELECT'。我在手冊中找不到任何有關此行爲的參考,因此我傾向於將其解釋爲一個錯誤。 – eggyal 2012-07-07 01:22:11

回答

2

MySQL正在重複使用在上次執行時準備的語句。它並不真正「緩存」列名;什麼是「緩存」(如果你願意)是準備好的聲明。

最簡單的解決方法是使用動態SQL語句在行爲進行控制,並避免先前準備的語句的重用:

SET @s=CONCAT('SELECT ',@mycol,' FROM mydata'); 
PREPARE stmt FROM @s; 
EXECUTE stmt; 
DEALLOCATE PREPARE stmt; 

這不是事情的列名是「緩存的「,或者查詢的結果被緩存。這是性能優化;這是你的會議中已經準備好的陳述的問題。


通過使用動態SQL,你可以當準備語句(即解析語法SQL文本控制(聲明形成,關鍵字等),檢查語義(對象名稱存在,列名存在,用戶所需的特權,等等),並準備執行計劃。

與靜態SQL,這一切發生在第一次執行,然後掛起的MySQL到事先準備好的聲明。

出於性能的考慮,每次執行靜態語句時,我們都不希望「硬解析」的開銷,這在fu中尤其如此從SQL語句中調用多次。但是,Oracle在做好引用對象更改或刪除操作時將預先準備好的語句標記爲INVALID做得不錯。)

MySQL選擇不這樣做,可能是因爲追蹤所有依賴項的開銷。而且,在絕大多數情況下,這種開銷不是必需的。

我認爲這裏的教訓是,如果您要使用動態SQL來創建一個將包含DIFFERENT列的表,您將不得不使用動態SQL來查詢該表。


我的建議是,你避免使用SELECT *,除非你的語句在列的完全控制權返回,例如,從內嵌視圖。否則,使用SELECT *的SQL語句將從根本上破壞...它們現在可能會正常工作,但對錶進行更改(例如添加一列)將會破壞應用程序。


問:請解釋它是如何不是一個錯誤。

這不是一個錯誤,因爲存儲過程中的SELECT語句實際上只是實際發生的簡寫。

在你的過程的第一次執行中,MySQL正在解析你的查詢文本,並準備和執行一個語句。基本上相當於:

PREPARE s1 FROM 'SELECT * FROM mydata'; 
EXECUTE s1; 

在該過程的第二次執行中,MySQL只是執行先前已準備好的語句。基本上,相當於:

EXECUTE s1; 

在那第二個執行,你似乎在期待的MySQL運行相當於:

DEALLOCATE PREPARE s1; 
PREPARE s1 FROM 'SELECT * FROM mydata'; 
EXECUTE s1; 

你可以的情況下,這是什麼樣的MySQL 應該是在第二次執行。你可能會認爲在過去的一個過程執行過程中準備的語句應該被丟棄,並在隨後的執行過程中重新解析和重新準備。

這對於DBMS來說是不會錯的。但是,一如既往,會考慮對績效的影響。

你也可以讓MySQL跟蹤一個特定的預備語句所依賴的所有數據庫對象。您可能會爭辯說,只要其中一個數據庫對象被刪除或更改,MySQL就會使依賴於已更改或已刪除對象的所有準備好的語句(以及所有其他對象)無效。再次,DBMS做到這一點不會有錯。一些DBMS(如Oracle)很好地完成了這項工作。但同樣,DBMS的開發人員在做出這些設計和實施決策時也會考慮性能。

底線是MySQL 確實爲您提供了一種方法來實現您想要發生的事情。這只是在你的程序中的語法,你期望實現它的事實並沒有真正實現。


首先它是一個臨時表,所以真的不應該期望在那裏,第二 - 它被丟棄

我認爲你正在閱讀不同的東西進入"TEMPORARY"關鍵字比被定義在說明書中。一個TEMPORARY表格實際上就像一個常規表格,只是它只對創建它的會話可見,並且在MySQL會話結束時會自動刪除。 (我們還注意到,TEMPORARY表不是由SHOW TABLES命令顯示的,並且不出現在information_schema視圖中。)

至於哪些表(TEMPORARY或其他)MySQL應該期待「在那裏」,我不要相信文檔確實解決了這個問題,除非注意到當執行SQL語句並且該語句引用不存在的對象時,MySQL將拋出異常。

您使用TEMPORARY表觀察同樣的行爲,您也會觀察到非TEMPORARY表。該問題與該表是否被定義爲TEMPORARY無關。


在哪裏呢SELECT *比較PREPARE s1 FROM SELECT *

這兩種形式有效地遵循相同的代碼路徑。靜態SELECT *第一次執行實際上相當於:

PREPARE s1 FROM 'SELECT *'; 
EXECUTE s1; 

(注意沒有DEALLOCATE聲明exeuction以下。)在隨後的執行,該聲明已經準備,所以它實際上相當於:

EXECUTE s1; 

這類似於會發生什麼,如果你是在PHP編碼的mysqli

$s1 = $mysqli->prepare("SELECT * FROM mydata"); 
$mysqli->execute($s1); 
/* rename the columns in the mydata table */ 
$mysqli->execute($s1); 
+0

這是通過「SELECT SQL_NO_CACHE * FROM mydata;」失敗的查詢 – 2012-07-07 00:20:27

+0

首次執行新會話時總是有效 - 因此它與會話相關。 – 2012-07-07 00:22:01

+0

我不同意你的教訓;-)恕我直言mysql已損壞,我可能不會接受你的答案,除非這證明是mysql錯誤 - 只是因爲你擁有的所有東西都是解決方法而不是回答原始問題。但是,謝謝你花時間看 – 2012-07-07 21:07:44

3

我明白ŧ他是相對較舊的(+6個月),但是我在準備好的語句中遇到了這個問題,並且我解決它的唯一方法是連接字段名稱,並使用準備好的語句有效地調用「select *」,但使用實際的字段名稱。我正在使用準備好的語句來創建一個臨時表,並且唯一的標準字段是第一個,而其他字段會導致「Select *」上的緩存問題。

  • 選擇字段,我們想通過字段名的錶行使用到一個臨時表
  • 迭代,並在每次迭代:

    集SQL01 = CONCAT(SQL01 '',sFieldName,''); (只是字段)和:set sql02 = concat(sql02,',',sFieldName,' varchar(50)'); (僅字段+字段類型,創建表語句)

  • 創建輸出表:

    組@sql =的concat( 'CREATE TEMPORARY TABLE tOutput(FirstField VARCHAR(50),',sql02,') ;');從@sql準備STMT; EXECUTE STMT;終止準備STMT;

  • 在結束:

    組@sql =的concat( 'SELECT FirstField,',SQL01, 'FROM tOutput;');從@sql準備STMT; EXECUTE STMT;終止準備STMT;