Umgehen MySQL-Fehler "Tabelle kann nicht erneut geöffnet werden"

88

Ich bin derzeit damit beschäftigt, eine Art Filter zu implementieren, für den ich für jedes "Tag", nach dem gefiltert werden soll, eine INNER JOIN-Klausel generieren muss.

Das Problem ist, dass ich nach einer ganzen Reihe von SQL eine Tabelle habe, die alle Informationen enthält, die ich für meine Auswahl benötige, aber ich benötige sie erneut für jeden generierten INNER JOIN

Das sieht im Grunde so aus:

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

Dies funktioniert, aber ich würde es sehr bevorzugen, wenn die "Suchtabelle" temporär ist (sie kann mehrere Größenordnungen kleiner sein, wenn es sich nicht um eine normale Tabelle handelt), aber das gibt mir einen sehr ärgerlichen Fehler: Can't reopen table

Einige Nachforschungen führen mich zu diesem Fehlerbericht, aber die Leute bei MySQL scheinen sich nicht darum zu kümmern, dass eine solche Grundfunktion (die eine Tabelle mehrmals verwendet) nicht mit temporären Tabellen funktioniert. Ich habe mit diesem Problem viele Probleme mit der Skalierbarkeit.

Gibt es eine praktikable Problemumgehung, bei der ich möglicherweise nicht viele temporäre, aber sehr reale Tabellen verwalten oder eine große Tabelle mit allen darin enthaltenen Daten verwalten muss?

Herzliche Grüße, Kris

[zusätzlich]

Die Antwort GROUP_CONCAT funktioniert in meiner Situation nicht, da meine Bedingungen mehrere Spalten in einer bestimmten Reihenfolge sind. Sie würde ORs aus dem machen, was ich als ANDs benötigen. Es hat mir jedoch geholfen, ein früheres Problem zu lösen, sodass die Tabelle, ob temporär oder nicht, jetzt nicht mehr benötigt wird. Wir dachten einfach zu allgemein für unser Problem. Die gesamte Anwendung von Filtern wurde jetzt von etwa einer Minute auf weit unter eine Viertelsekunde zurückgeführt.

Kris
quelle
2
Ich hatte das gleiche Problem, eine temporäre Tabelle zweimal in derselben Abfrage mit UNION zu verwenden.
Sebastián Grignoli

Antworten:

121

Eine einfache Lösung besteht darin, die temporäre Tabelle zu duplizieren. Funktioniert gut, wenn die Tabelle relativ klein ist, was häufig bei temporären Tabellen der Fall ist.

Pete
quelle
8
Sollte eigentlich die gewählte Antwort sein, da dies das Problem beantwortet, ohne herumzugehen.
Dyesdyes
3
Irgendwelche Ratschläge, wie Sie die Tabelle duplizieren würden? (Ich meine eine Art zu kopieren, ohne die Abfrage zu wiederholen)
Hernán Eche
16
Selbst wenn die temporäre Tabelle groß ist, sollte Ihnen der Cache von MySQL helfen. Was das Kopieren von einer temporären Tabelle in eine andere betrifft, sollte dies mit einer einfachen "CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1" durchgeführt werden.
AS7K
2
Wenn Sie den verlockenden Inhalt kopieren, vergessen Sie nicht, auch Indizes zu erstellen. Andernfalls kann Ihre Abfrage recht langsam sein.
Gaborsch
1
@NgSekLong Ja. Die ganze Zeit. Es hängt natürlich von Ihrer Anwendung für die Abfrage ab, aber ich sehe keine "großen" Leistungsprobleme bis> 100.000. In einem ETL-Prozess verwende ich diese Methode mit einer 3,5-mil-Tabelle. Die Geschwindigkeit dieser Anwendung ist jedoch nicht so wichtig.
Tanner Clark
49

Richtig, in den MySQL- Dokumenten heißt es: "Sie können TEMPORARYin derselben Abfrage nicht mehr als einmal auf eine Tabelle verweisen ."

Hier ist eine alternative Abfrage, die dieselben Zeilen finden sollte, obwohl sich alle Bedingungen für übereinstimmende Zeilen nicht in separaten Spalten befinden, sondern in einer durch Kommas getrennten Liste.

SELECT f1.baseID, GROUP_CONCAT(f1.condition)
FROM search f1
WHERE f1.condition IN (<condition1>, <condition2>, ... <conditionN>)
GROUP BY f1.baseID
HAVING COUNT(*) = <N>;
Bill Karwin
quelle
2
Dies löste mein Problem nicht wirklich, aber es ermöglichte mir, das Problem, das es verursachte, zu vereinfachen und so die Notwendigkeit des Versuchbaren zu negieren. Vielen Dank!
Kris
6

Ich habe dies umgangen, indem ich eine permanente "temporäre" Tabelle erstellt und die SPID (sorry, ich komme aus dem SQL Server-Land) an den Tabellennamen angehängt habe, um einen eindeutigen Tabellennamen zu erstellen. Erstellen Sie anschließend dynamische SQL-Anweisungen, um die Abfragen zu erstellen. Wenn etwas Schlimmes passiert, wird die Tabelle gelöscht und neu erstellt.

Ich hoffe auf eine bessere Option. Komm schon, MySQL Devs. Die 'Bug' / 'Feature Request' ist seit 2008 geöffnet! Scheint, als ob sich alle 'Bugs', denen wir begegnet sind, im selben Boot befinden.

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;
beeks
quelle
Hoffentlich kann sie MySQL jetzt, da Oracle die Regierungsgeschäfte übernimmt, einen guten Schub geben.
Pacerier
2
Seufzer Ich bezweifle es :(
BEEKS
3
Ein großer Seufzer . Juli 2016, und dieser temporäre Tabellenfehler ist immer noch nicht behoben. Ich werde wahrscheinlich eine Sequenznummer finden, die mit einem permanenten Tabellennamen (ich komme aus Oracle) verbunden ist, um dieses Problem zu umgehen.
TheWalkingData
Hattrick seufzt ... Es könnte nie repariert werden, da es bereits 2019 ist.
Zimano
3

Persönlich würde ich es einfach zu einem dauerhaften Tisch machen. Möglicherweise möchten Sie eine separate Datenbank für diese Tabellen erstellen (vermutlich benötigen sie eindeutige Namen, da viele dieser Abfragen gleichzeitig ausgeführt werden können), damit Berechtigungen auch sinnvoll festgelegt werden können (Sie können Berechtigungen für Datenbanken festlegen; Sie können ' t Berechtigungen für Tabellen-Platzhalter festlegen).

Dann benötigen Sie auch einen Bereinigungsjob, um gelegentlich alte zu entfernen (MySQL merkt sich bequemerweise die Zeit, zu der eine Tabelle erstellt wurde, sodass Sie damit einfach herausfinden können, wann eine Bereinigung erforderlich ist).

MarkR
quelle
9
Temporäre Tabellen haben den extremen Vorteil, dass mehrere Abfragen gleichzeitig ausgeführt werden können. Dies ist mit permanenten Tabellen nicht möglich.
Pacerier
Ich denke, die permanente Tabelle "Lösung" ist keine Lösung. Es löst das Problem sicher, ist aber nicht praktisch. Es tauchen so viele Fragen auf: Wie erstelle ich mehrere gleichzeitig? Wie würden Sie mit der Namenskonvention umgehen und gleichnamige Tabellen überschreiben? Wie wird die permanente Tabelle gelöscht? Wenn Sie bei der Beantwortung dieser Fragen anhand von permanenten Tabellen eine praktikable Lösung erarbeiten könnten, bin ich ganz Ohr!
Tanner Clark
0

Ich konnte die Abfrage in eine permanente Tabelle ändern und dies hat sie für mich behoben. (Die VLDB-Einstellungen in MicroStrategy, temporärer Tabellentyp, wurden geändert.)


quelle
0

Sie können dies umgehen, indem Sie entweder eine permanente Tabelle erstellen, die Sie anschließend entfernen, oder einfach zwei separate temporäre Tabellen mit denselben Daten erstellen

Inc33
quelle