我把问题简化为这个简单的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;
答案 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;