0

問題をこの単純な 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)

ストアドプロシージャを再度作成すると、機能します

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

2 に答える 2

3

これは比較的古い (+6 か月) ことは理解していますが、準備済みステートメントでこの問題に遭遇しました。これを回避する唯一の方法は、フィールド名を連結し、「select *」を効果的に呼び出す準備済みステートメントを使用することでしたが、実際のフィールド名。準備済みステートメントを使用して一時テーブルを作成しています。標準フィールドは最初のフィールドのみで、残りは " Select *" でキャッシュの問題を引き起こします。

  • 一時テーブルに使用するフィールドを選択します
  • フィールド名のテーブル行を反復し、各反復で:

    set sql01 = concat(sql01,', ',sFieldName,''); (フィールドのみ) および: set sql02 = concat(sql02,', ',sFieldName,'varchar(50) '); (フィールド + フィールド タイプのみ、create table ステートメントの場合)

  • 出力テーブルを作成します。

    set @sql = concat('CREATE TEMPORARY TABLE tOutput(FirstField varchar(50), ',sql02,');'); @sql から STMT を準備します。EXECUTE STMT; DEALLOCATE PREPARE STMT;

  • 最後に:

    set @sql = concat('SELECT FirstField,',sql01,' FROM tOutput;'); @sql から STMT を準備します。EXECUTE STMT; DEALLOCATE PREPARE STMT;

于 2013-03-15T15:48:28.863 に答える
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 *たとえば、インライン ビューから返される列をステートメントが完全に制御している場合を除き、を使用しないことをお勧めします。それ以外の場合、使用する SQL ステートメントSELECT *は根本的に壊れています... 現在は機能している可能性がありますが、テーブルへの変更 (たとえば、列の追加) はアプリケーションを壊します。


Q:バグではない理由を教えてください。

ストアド プロシージャ内の SELECT ステートメントは、実際に起こっていることの省略形であるため、これはバグではありません。

プロシージャの最初の実行時に、MySQL はクエリ テキストの解析を行い、ステートメントを準備して実行します。基本的に、次と同等です。

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

プロシージャの 2 回目の実行では、MySQL は以前に準備されたステートメントを実行しているだけです。基本的に、次と同等です。

EXECUTE s1;

その 2 回目の実行では、MySQL が次のように実行されることを期待しているようです。

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

これが、MySQLが 2 回目の実行で行うべきことであると主張できます。プロシージャの以前の実行中に準備されたステートメントは破棄され、後続の実行で再解析および再準備されるべきであると主張することができます。

DBMS がこれを行うのは間違いではありません。しかし、いつものように、パフォーマンスへの影響に関する考慮事項があります。

特定のプリペアド ステートメントが依存しているすべてのデータベース オブジェクトを MySQL が追跡する必要があると主張することもできます。これらのデータベース オブジェクトの 1 つが削除または変更されるたびに、MySQL は、変更または削除されたオブジェクトに依存するすべての準備済みステートメント (および他のすべてのオブジェクト) を無効にする必要があると主張することができます。繰り返しますが、DBMS がこれを行うのは間違いではありません。一部の DBMS (Oracle など) は、これを非常にうまく実行します。ただし、DBMS の開発者は、これらの設計と実装の決定を行う際に、パフォーマンスも考慮に入れます。

要するに、MySQL は、ユーザー望むことを実現する方法を提供するということです。プロシージャの構文、それを実現することを期待しているものは、実際には実現しないというだけです。


まず第一に、それは一時テーブルであるため、そこに存在することを実際に期待するべきではありません.2番目-削除されます

"TEMPORARY"仕様で定義されているものとは異なるキーワードを読み取っていると思います。テーブルは、それを作成したセッションにのみ表示され、MySQL セッションが終了すると自動的に削除されることを除いて、通常のTEMPORARYテーブルとまったく同じです。TEMPORARY(テーブルは SHOW TABLES コマンドでは表示されず、 information_schema ビューにも表示されないことにも注意してください。)

どのテーブル (TEMPORARY またはそれ以外) の MySQL が「そこにある」ことを期待する必要があるかについては、SQL ステートメントが実行されたとき、そのステートメントが存在しないオブジェクトを参照していることに注意することを除いて、ドキュメントが実際にそれに対処しているとは思いません。 MySQL は例外をスローします。

TEMPORARY テーブルで見られるのと同じ動作が、非 TEMPORARY テーブルでも見られます。この問題は、テーブルが TEMPORARY として定義されているかどうかには関係ありません。


どこSELECT *と比べて PREPARE s1 FROM SELECT *

これら 2 つの形式は、実質的に同じコード パスに従います。static の最初の実行は、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);
于 2012-07-06T23:56:26.167 に答える