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

Posted

技术标签:

【中文标题】绕过 MySQL“无法重新打开表”错误【英文标题】:Getting around MySQL "Can't reopen table" error 【发布时间】:2010-09-25 11:55:58 【问题描述】:

我目前正忙于实现一个过滤器,我需要为每个“标签”生成一个 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

一些研究将我引向this bug report,但 mysql 的人们似乎并不关心这样的基本功能(多次使用表)不适用于临时表。我在这个问题上遇到了很多可扩展性问题。

是否有任何可行的解决方法不需要我管理潜在的大量临时但非常真实的表,或者让我维护一个包含所有数据的巨大表?

亲切的问候,克里斯

[附加]

GROUP_CONCAT 答案在我的情况下不起作用,因为我的条件是按特定顺序排列的多个列,它会使 OR 与我需要的 AND 无关。但是,它确实帮助我解决了早期的问题,所以现在不再需要这个表,无论是否是临时的。对于我们的问题,我们只是想得太笼统了。过滤器的整个应用现在已经从大约一分钟缩短到不到四分之一秒。

【问题讨论】:

我在使用 UNION 的同一查询中两次使用临时表时遇到了同样的问题。 那我就用一张真正的桌子。 【参考方案1】:

如果切换到 MariaDB(MySQL 的一个分支)是可行的 - 从版本 10.2.1 开始,这个烦恼已经得到解决:https://jira.mariadb.org/browse/MDEV-5535。

【讨论】:

【参考方案2】:

这里是关于这个问题的 MYSQL 文档。我像上面的一些答案一样使用重复的临时表,但是,您可能会遇到适合 CTE 的情况!

https://dev.mysql.com/doc/refman/8.0/en/temporary-table-problems.html

【讨论】:

cte 示例对我帮助很大。谢谢!【参考方案3】:

您可以通过创建一个永久表(之后将其删除)来解决此问题,或者只使用相同的数据创建 2 个单独的临时表

【讨论】:

【参考方案4】:

一个简单的解决方案是复制临时表。如果表相对较小,则效果很好,临时表通常是这种情况。

【讨论】:

实际上应该是选择的答案,因为这可以解决问题,而无需绕过。 关于如何复制表格的任何建议? (我的意思是一种复制不重复查询的方式) 即使临时表很大,mysql 的缓存也能帮到你。至于从一个临时表复制到另一个,一个简单的“CREATE TEMPORARY TABLE tmp2 SELECT * FROM tmp1”应该可以做到。 如果你复制了临时的内容,别忘了创建索引,否则你的查询可能会很慢。 @NgSekLong 是的。每时每刻。这显然取决于您的查询应用程序,但直到> 100,000,我才看到“巨大的”性能问题。在一个 ETL 过程中,我将此方法用于 3.5 万张表。不过,该应用程序的速度并不那么重要。【参考方案5】:

我通过创建一个永久的“临时”表并将 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 一个很好的推动力。 一个大大的叹息。 2016 年 7 月,这个临时表错误仍未修复。我可能会想出某种与永久表名连接的序列号(我来自 Oracle 领域)来规避这个问题。 Hattrick 叹息......它可能永远不会被修复,因为它已经是 2019 年了。【参考方案6】:

我能够将查询更改为永久表,这为我修复了它。 (更改了 MicroStrategy 中的 VLDB 设置,临时表类型)。

【讨论】:

【参考方案7】:

就我个人而言,我只是把它做成一张永久的桌子。您可能希望为这些表创建一个单独的数据库(假设它们需要唯一的名称,因为可以一次完成许多这些查询),还允许明智地设置权限(您可以设置数据库的权限;您可以t 设置表通配符的权限)。

那么你还需要一个清理工作来偶尔删除旧的(MySQL 方便地记住一个表的创建时间,所以你可以用它来计算何时需要清理)

【讨论】:

临时表的最大优势是可以同时运行多个查询。这对于永久表是不可能的。 我认为永久表“解决方案”不是解决方案。它确实解决了问题,但不实用。出现了很多问题:我如何同时创建多个?您将如何处理命名约定并覆盖相同的命名表?删除永久表的流程是什么?如果您在回答这些问题时可以使用永久表格详细说明可行的解决方案,我会全力以赴!【参考方案8】:

没错,MySQL docs 说:“你不能在同一个查询中多次引用 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>;

【讨论】:

这实际上并没有解决我手头的问题,但它确实使我能够简化导致它的问题,从而消除了对 temptable 的需求。谢谢!

以上是关于绕过 MySQL“无法重新打开表”错误的主要内容,如果未能解决你的问题,请参考以下文章

MySQL绕过错误1093

HttpClient 无法忽略或绕过自签名证书错误

MYSQL搜索条件的微小差异会产生巨大的差异,无法绕过它

绕过错误 C2248“无法访问在类中声明的受保护成员”的有效方法

mysql. 小括号绕过

项目实战 | 报错SQL注入绕过WAF