2012-07-06 79 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;