web-dev-qa-db-ja.com

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

私は現在、フィルタリングするすべての「タグ」に対して内部結合句を生成する必要がある種類のフィルターの実装に忙しくしています。

問題は、大量の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を作成します。しかし、それは以前の問題を解決するのに役立ちました。そのため、一時テーブルであるかどうかに関係なく、テーブルは不要になりました。私たちは問題に対して一般的すぎると考えていました。フィルターの適用全体が約1分から4分の1秒以下に戻りました。

81
Kris

そう、MySQL docs say: "同じクエリで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>;
45
Bill Karwin

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

105
Pete

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

より良いオプションを望んでいます。 C'mon、MySQL開発者。 「バグ」/「機能リクエスト」は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;
6
beeks

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

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

3
MarkR

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

1
Cary

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

0
Inc33