MySQLの「テーブルを再オープンできません」エラーの回避


88

私は現在、フィルターをかける「タグ」ごとにINNER JOIN句を生成する必要がある種類のフィルターの実装に忙しい。

問題は、大量のSQLを実行した後、選択するために必要なすべての情報を含むテーブルがあることですが、生成されたすべてのINNER JOINに対して再び必要です。

これは基本的に次のようになります。

SELECT
    *
FROM search
INNER JOIN search f1 ON f1.baseID = search.baseID AND f1.condition = condition1
INNER JOIN search f2 ON f2.baseID = search.baseID AND f2.condition = condition2
...
INNER JOIN search fN ON fN.baseID = search.baseID AND fN.condition = conditionN

これは機能しますが、「検索」テーブルを一時的なものにすることを強くお勧めします(通常のテーブルではない場合は、数桁小さい場合があります)が、非常に煩わしいエラーが発生します。 Can't reopen table

いくつかの調査でこのバグレポートが表示されましたが、MySQLの担当者は、このような基本的な機能(テーブルを複数回使用する)が一時テーブルで機能しないことを気にしていません。私はこの問題で多くのスケーラビリティの問題に直面しています。

潜在的に大量の一時的な非常に実際のテーブルを管理したり、すべてのデータを含む巨大なテーブルを維持したりする必要のない実行可能な回避策はありますか?

敬具、クリス

【追加】

条件が特定の順序で複数の列になっているため、GROUP_CONCAT回答は私の状況では機能しません。ANDにする必要があるものからORを作成します。しかし、それは以前の問題を解決するのに役立ちましたので、tempかどうかに関わらず、テーブルは必要なくなりました。私たちは問題に対して一般的すぎると考えていました。フィルターの適用全体が、1分程度から1/4秒未満に戻されました。


2
UNIONを使用して同じクエリで一時テーブルを2回使用すると、同じ問題が発生しました。
セバスティアンGrignoli

回答:



122

簡単な解決策は、一時テーブルを複製することです。テーブルが比較的小さい場合に適切に機能します。これは、一時テーブルでよくあることです。


8
これは回避せずに問題を解決するので、実際に選択した答えになるはずです。
dyesdyes 2014

4
テーブルをどのように複製するかについてのアドバイスはありますか?(私は、クエリを繰り返していないコピーの方法を意味する)
エルナンEche

16
一時テーブルが大きい場合でも、mysqlのキャッシュが役立ちます。ある一時テーブルから別の一時テーブルへのコピーに関しては、単純な「CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1」で実行する必要があります。
AS7K 2016年

2
魅力的なコンテンツをコピーする場合は、インデックスも作成することを忘れないでください。そうしないと、クエリがかなり遅くなる可能性があります。
gaborsch 2017

1
@NgSekLongはい。いつも。クエリのアプリケーションに依存しますが、100,000を超えるまでは「巨大な」パフォーマンスの問題は発生しません。1つのETLプロセスで、この方法を3.5milテーブルで使用します。ただし、そのアプリケーションの速度はそれほど重要ではありません。
Tanner Clark、

49

MySQLのドキュメントTEMPORARYでは、「同じクエリでテーブルを複数回参照することはできません」と述べています。

同じ行を見つける必要がある別のクエリを次に示します。一致する行のすべての条件が個別の列にあるわけではありませんが、コンマ区切りのリストになります。

SELECT f1.baseID, GROUP_CONCAT(f1.condition)
FROM search f1
WHERE f1.condition IN (<condition1>, <condition2>, ... <conditionN>)
GROUP BY f1.baseID
HAVING COUNT(*) = <N>;

2
これで実際に私の問題を解決することはできませんでしたが、それによって問題の原因となった問題を単純化することができたため、誘惑の可能性をなくすことができました。ありがとう!
クリス

6

これを回避するには、永続的な「一時」テーブルを作成し、SPID(申し訳ありませんが、SQL Serverの土地です)をテーブル名に追加して、一意のテーブル名を作成しました。次に、動的SQLステートメントを作成してクエリを作成します。何か悪いことが起こった場合、テーブルは削除され、再作成されます。

より良いオプションを期待しています。おい、MySQL Devs。'バグ' / '機能リクエスト'は2008年からオープンしています!遭遇したすべての「バグ」は同じボートにあるようです。

select concat('ReviewLatency', CONNECTION_ID()) into @tablename;

#Drop "temporary" table if it exists
set @dsql=concat('drop table if exists ', @tablename, ';');
PREPARE QUERY1 FROM @dsql;
EXECUTE QUERY1;
DEALLOCATE PREPARE QUERY1;

#Due to MySQL bug not allowing multiple queries in DSQL, we have to break it up...
#Also due to MySQL bug, you cannot join a temporary table to itself,
#so we create a real table, but append the SPID to it for uniqueness.
set @dsql=concat('
create table ', @tablename, ' (
    `EventUID` int(11) not null,
    `EventTimestamp` datetime not null,
    `HasAudit` bit not null,
    `GroupName` varchar(255) not null,
    `UserID` int(11) not null,
    `EventAuditUID` int(11) null,
    `ReviewerName` varchar(255) null,
    index `tmp_', @tablename, '_EventUID` (`EventUID` asc),
    index `tmp_', @tablename, '_EventAuditUID` (`EventAuditUID` asc),
    index `tmp_', @tablename, '_EventUID_EventTimestamp` (`EventUID`, `EventTimestamp`)
) ENGINE=MEMORY;');
PREPARE QUERY2 FROM @dsql;
EXECUTE QUERY2;
DEALLOCATE PREPARE QUERY2;

#Insert into the "temporary" table
set @dsql=concat('
insert into ', @tablename, ' 
select e.EventUID, e.EventTimestamp, e.HasAudit, gn.GroupName, epi.UserID, eai.EventUID as `EventAuditUID`
    , concat(concat(concat(max(concat('' '', ui.UserPropertyValue)), '' (''), ut.UserName), '')'') as `ReviewerName`
from EventCore e
    inner join EventParticipantInformation epi on e.EventUID = epi.EventUID and epi.TypeClass=''FROM''
    inner join UserGroupRelation ugr on epi.UserID = ugr.UserID and e.EventTimestamp between ugr.EffectiveStartDate and ugr.EffectiveEndDate 
    inner join GroupNames gn on ugr.GroupID = gn.GroupID
    left outer join EventAuditInformation eai on e.EventUID = eai.EventUID
    left outer join UserTable ut on eai.UserID = ut.UserID
    left outer join UserInformation ui on eai.UserID = ui.UserID and ui.UserProperty=-10
    where e.EventTimestamp between @StartDate and @EndDate
        and e.SenderSID = @FirmID
    group by e.EventUID;');
PREPARE QUERY3 FROM @dsql;
EXECUTE QUERY3;
DEALLOCATE PREPARE QUERY3;

#Generate the actual query to return results. 
set @dsql=concat('
select rl1.GroupName as `Group`, coalesce(max(rl1.ReviewerName), '''') as `Reviewer(s)`, count(distinct rl1.EventUID) as `Total Events`
    , (count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) as `Unreviewed Events`
    , round(((count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) / count(distinct rl1.EventUID)) * 100, 1) as `% Unreviewed`
    , date_format(min(rl2.EventTimestamp), ''%W, %b %c %Y %r'') as `Oldest Unreviewed`
    , count(distinct rl3.EventUID) as `<=7 Days Unreviewed`
    , count(distinct rl4.EventUID) as `8-14 Days Unreviewed`
    , count(distinct rl5.EventUID) as `>14 Days Unreviewed`
from ', @tablename, ' rl1
left outer join ', @tablename, ' rl2 on rl1.EventUID = rl2.EventUID and rl2.EventAuditUID is null
left outer join ', @tablename, ' rl3 on rl1.EventUID = rl3.EventUID and rl3.EventAuditUID is null and rl1.EventTimestamp > DATE_SUB(NOW(), INTERVAL 7 DAY) 
left outer join ', @tablename, ' rl4 on rl1.EventUID = rl4.EventUID and rl4.EventAuditUID is null and rl1.EventTimestamp between DATE_SUB(NOW(), INTERVAL 7 DAY) and DATE_SUB(NOW(), INTERVAL 14 DAY)
left outer join ', @tablename, ' rl5 on rl1.EventUID = rl5.EventUID and rl5.EventAuditUID is null and rl1.EventTimestamp < DATE_SUB(NOW(), INTERVAL 14 DAY)
group by rl1.GroupName
order by ((count(distinct rl1.EventUID) - count(distinct rl1.EventAuditUID)) / count(distinct rl1.EventUID)) * 100 desc
;');
PREPARE QUERY4 FROM @dsql;
EXECUTE QUERY4;
DEALLOCATE PREPARE QUERY4;

#Drop "temporary" table
set @dsql = concat('drop table if exists ', @tablename, ';');
PREPARE QUERY5 FROM @dsql;
EXECUTE QUERY5;
DEALLOCATE PREPARE QUERY5;

うまくいけば、オラクルが統治を引き継ぐようになったので、彼女はMySQLに良い推進力を与えることができます。
Pacerier

2
ため息私はそれを疑う:(
ビーク

3
大きなため息。2016年7月、この一時テーブルのバグはまだ修正されていません。おそらく、この問題を回避するために、永続的なテーブル名と連結したある種のシーケンス番号(私はOracleの出身です)を思い付くでしょう。
TheWalkingData 2016

Hattrickため息...それは修正されない可能性があり、2019年をすでに見ています。
Zimano

3

個人的には私はそれを永続的なテーブルにします。これらのテーブル用に個別のデータベースを作成することもできます(これらのクエリの多くは一度に実行できるため、一意の名前が必要になる可能性があります)。また、アクセス許可を適切に設定できるようにすることもできます(データベースにアクセス許可を設定できます。 tテーブルのワイルドカードに権限を設定します)。

次に、古いものを時々削除するためのクリーンアップジョブも必要です(MySQLはテーブルが作成された時刻を便利に記憶しているので、クリーンアップが必要なときにそれを使用して作業できます)


9
一時テーブルには、複数のクエリを同時に実行できるという非常に大きな利点があります。これは、永続テーブルでは不可能です。
Pacerier

永続テーブルの「解決策」は解決策ではないと思います。それは確かに問題を解決しますが、実用的ではありません。非常に多くの質問が浮かび上がります。どうすれば同時に複数を作成できますか 命名規則をどのように扱い、同じ名前のテーブルを上書きしますか?永続テーブルを削除するプロセスは何ですか?これらの質問に答えながら、パーマネントテーブルを使用して実現可能な解決策について詳しく説明できれば、私はすべて耳を傾けます!
タナークラーク

0

クエリを永続的なテーブルに変更することができ、これで修正されました。(MicroStrategyのVLDB設定、一時テーブルタイプが変更されました)。


-1

後で削除する永続テーブルを作成するか、同じデータで2つの個別の一時テーブルを作成することで回避できます。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.