为什么mysql缓存被删除的临时表的列名?

为什么mysql缓存被删除的临时表的列名?,mysql,stored-procedures,Mysql,Stored Procedures,我已将问题简化为这个简单的SP。列名将缓存在最后的SELECT*中。我不知道为什么或者如何阻止它。我尝试添加SQL\u NO\u缓存,但没有任何区别 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 DEFINER=root@local

我已将问题简化为这个简单的SP。列名将缓存在最后的SELECT*中。我不知道为什么或者如何阻止它。我尝试添加SQL\u NO\u缓存,但没有任何区别

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 DEFINER=root@localhost 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(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)
现在,如果我尝试使用另一列再次运行它

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)
如果我重新创建storedprocedure它的工作

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)
但是如果我尝试切换回第一列——即使我尝试先删除临时表——它仍然不起作用

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>
*eggyal要求的其他信息。我也在另一个mysql版本上尝试了这个方法,得到了相同的结果。*

有趣的修复程序开发—将最后几行更改为一个已准备好的语句—但使用与以前完全相同的查询

-- 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;

MySQL正在重用上次执行时准备的语句。它不是真正的“缓存”列名;它的“缓存”(如果您愿意)是准备好的语句

简单的解决方法是使用动态SQL语句来控制行为,并避免重用先前准备的语句:

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

这与“缓存”列名或缓存查询结果无关。这是一种性能优化;这是一个已经在你的会议中准备好的声明的问题


通过使用动态SQL,您可以控制何时准备语句(即解析SQL文本的语法(语句格式、关键字等)、检查语义(存在对象名、列名、用户具有所需权限等)以及准备执行计划

对于静态SQL,所有这些都发生在第一次执行时,然后MySQL挂起准备好的语句

出于性能方面的考虑,我们不希望每次执行静态语句时都出现“硬解析”的开销。对于从SQL语句多次调用的函数尤其如此

(注意:Oracle也做了同样的事情,但是,每当被引用的对象被更改或删除时,Oracle很好地将准备好的语句标记为无效。)

MySQL选择不这样做,可能是因为跟踪所有依赖项的开销。而且,在绝大多数情况下,这种开销是不需要的

我认为这里的教训是,如果要使用动态SQL创建一个表,其中将包含不同的列,则必须使用动态SQL查询该表


我的建议是避免使用
SELECT*
,除非您的语句完全控制从内联视图返回的列。否则,使用
SELECT*
的SQL语句从根本上被破坏了……它们现在可以工作了,但需要更改表(例如添加列)将破坏应用程序



问:请解释为什么它不是一个bug

这不是一个bug,因为存储过程中的SELECT语句实际上只是实际发生情况的简写

在第一次执行您的过程时,MySQL正在解析您的查询文本,并准备和执行一条语句。基本上相当于:

PREPARE s1 FROM 'SELECT * FROM mydata';
EXECUTE s1;
在该过程的第二次执行中,MySQL只是执行之前准备好的语句,基本上相当于:

EXECUTE s1;
PREPARE s1 FROM 'SELECT *';
EXECUTE s1;
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会话结束时会自动删除。(我们还注意到,
临时
表不会通过SHOW TABLES命令显示,也不会出现在信息模式视图中。)

至于哪些表(临时的或其他的)MySQL应该期望“存在”,我不相信文档真的解决了这个问题,除非注意到当执行SQL语句时,该语句引用一个不存在的对象,MySQL将抛出一个异常

与您在临时表中观察到的行为相同,您也将在非临时表中观察到。问题与该表是否定义为临时表无关


SELECT*
PREPARE s1 FROM SELECT*

这两种形式实际上遵循相同的代码路径。静态
SELECT*
的第一次执行实际上相当于
EXECUTE s1;
$s1 = $mysqli->prepare("SELECT * FROM mydata");
$mysqli->execute($s1);
/* rename the columns in the mydata table */
$mysqli->execute($s1);