绕过MySQL的“无法重新打开表”错误

113

我目前正忙于实现一种过滤器,需要为每个“标签”生成一个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 一些研究引导我到这个bug报告,但MySQL的人似乎不在意这样一个基本功能(使用一个表超过一次),在临时表中不起作用。我遇到了许多与此问题相关的可扩展性问题。
是否有任何可行的解决方法,不需要我管理可能有很多临时但非常真实的表格,或者让我维护一个包含所有数据的巨大表格?
顺祝商祺,Kris
[额外说明]
GROUP_CONCAT答案在我的情况下不起作用,因为我的条件是特定顺序的多个列,它会将我需要的AND变成OR。然而,它确实帮助我解决了一个早期的问题,所以现在,无论是临时表还是非临时表,都不再需要了。我们只是为我们的问题想得太泛泛了。整个过滤应用程序现在已经从大约一分钟的时间缩短到不到四分之一秒。

7
在同一个查询中使用UNION时,我遇到了相同的问题,即临时表被使用了两次。 - Sebastián Grignoli
我将使用真实的数据表。 - The Onin
请注意,这在MariaDB上是不会发生的。 - medilies
8个回答

150

简单的解决方案是复制临时表。如果临时表相对较小,这通常是有效的。


9
实际上应该选择这个答案作为最佳答案,因为它直接回答了问题,没有绕弯子。 - dyesdyes
6
您有关于如何复制表格的建议吗?(我的意思是一种复制而不是重复查询的方法) - Hernán Eche
22
即使临时表很大,MySQL的缓存也应该能够帮助您。至于从一个临时表复制到另一个临时表,则可以使用简单的“CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1”命令。 - AS7K
3
如果你复制temptable的内容,请不要忘记创建索引,否则你的查询速度可能会非常慢。 - gaborsch
2
@NgSekLong 是的,一直都是。显然这取决于您查询的应用程序,但在超过100,000条记录之前,我并没有看到“巨大”的性能问题。在一个ETL过程中,我使用这种方法处理了一个350万行的表格。虽然该应用程序的速度不是很重要。 - Tanner Clark
显示剩余4条评论

53

正确的,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
这并没有真正解决我的问题,但它使我能够简化导致问题的原因,从而消除了临时表的需要。谢谢! - Kris

9
我通过创建一个永久的“临时”表并在表名后缀SPID(抱歉,我来自SQL Server领域),以使表名唯一的方式解决了这个问题。然后创建动态SQL语句来创建查询。如果发生任何不良情况,表将被删除并重新创建。
我希望有更好的选择。来吧,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;

希望现在有了 Oracle 掌管,她能够给 MySQL 一个良好的推动。 - Pacerier
4
一声长叹。到了2016年7月,这个临时表的漏洞仍未得到修复。我可能会想出一种序列号与永久表名进行拼接的方法(我来自Oracle领域),以绕过这个问题。 - TheWalkingData
2
Hattrick 叹气...可能永远都不会修复,毕竟已经是2019年了。 - Zimano

5

4

我个人会将其设置为永久表格。你可能需要为这些表格创建一个单独的数据库(假设它们需要唯一的名称,因为很多查询可能同时执行),同时也可以合理地设置权限(您可以设置数据库的权限;不能设置表通配符的权限)。

然后您还需要定期清理旧表(MySQL方便地记住表格创建的时间,所以您可以利用这一点来确定何时需要进行清理)。


10
临时表具有极大的优势,您可以同时运行多个查询。这在永久表中是不可能的。 - Pacerier
我认为永久表“solution”并不是一个完美的解决方案。它确实解决了问题,但并不实用。有很多问题需要考虑:如何同时创建多个永久表?如何处理命名约定和同名表格的覆盖?删除永久表的过程是什么?如果你能在回答这些问题的同时详细说明使用永久表的可行解决方案,我将全神贯注! - Tanner Clark

1
你可以通过创建一个永久表格,之后再删除它,或者创建两个相同数据的临时表格来绕过此问题。

1

cte的例子对我帮助很大,谢谢! - vdidxho

0

我成功将查询更改为永久表,这解决了我的问题。(在MicroStrategy中更改了VLDB设置,临时表类型)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接