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

时间:2012-07-06 22:47:05

标签: mysql stored-procedures

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

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版本。 *

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;

2 个答案:

答案 0 :(得分:3)

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都会将准备好的语句标记为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会话结束时会自动删除。 (我们还注意到SHOW TABLES命令不显示TEMPORARY表,并且不会出现在information_schema视图中。)

关于MySQL应该期望哪些表(TEMPORARY或其他)#34;在那里,我不相信文档真正解决了这个问题,除了注意到执行SQL语句时,该语句引用了一个不存在的对象,MySQL将引发异常。

使用TEMPORARY表观察到的行为相同,您还将使用非TEMPORARY表进行观察。该问题与表是否定义为TEMPORARY无关。


SELECT * PREPARE s1 FROM SELECT *进行比较

这两种形式有效地遵循相同的代码路径。第一次执行静态SELECT *实际上等同于:

PREPARE s1 FROM 'SELECT *';
EXECUTE s1;

(注意在执行后没有DEALLOCATE语句。)在随后的执行中,语句已经准备好,因此它实际上等同于:

EXECUTE s1;

这类似于使用PHP mysqli

进行编码时会发生的情况
$s1 = $mysqli->prepare("SELECT * FROM mydata");
$mysqli->execute($s1);
/* rename the columns in the mydata table */
$mysqli->execute($s1);

答案 1 :(得分:3)

我理解这是相对较旧的(+6个月),但我在准备好的语句中遇到了这个问题,我解决它的唯一方法是连接字段名称并使用有效调用“select *”的预准备语句但是使用实际的字段名称。我正在使用预准备语句来创建临时表,唯一的标准字段是第一个,而其余的则导致“Select *”上的缓存问题。

  • 选择我们要用于临时表的字段
  • 遍历字段名称的表行,并在每次迭代中:

    set sql01 = concat(sql01,',',sFieldName,''); (只是字段)和:set sql02 = concat(sql02,',',sFieldName,' varchar(50)'); (仅限字段+字段类型,用于创建表语句)

  • 创建输出表:

    设置@sql = concat('CREATE TEMPORARY TABLE tOutput(FirstField varchar(50),',sql02,');');从@sql预备STMT;执行STMT; DEALLOCATE PREPARE STMT;

  • 最后:

    设置@sql = concat('SELECT FirstField,',sql01,'FROM tOutput;');从@sql预备STMT;执行STMT; DEALLOCATE PREPARE STMT;