Gặp lỗi "Không thể mở lại bảng" trong MySQL


88

Tôi hiện đang bận triển khai một bộ lọc các loại mà tôi cần tạo một mệnh đề INNER JOIN cho mọi "thẻ" để lọc.

Vấn đề là sau một loạt SQL, tôi có một bảng chứa tất cả thông tin tôi cần để thực hiện lựa chọn của mình, nhưng tôi cần lại nó cho mỗi INNER JOIN được tạo

Về cơ bản điều này trông giống như:

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

Điều này hoạt động nhưng tôi muốn bảng "tìm kiếm" chỉ là tạm thời (nó có thể nhỏ hơn một số bậc lớn hơn nếu nó không phải là bảng bình thường) nhưng điều đó mang lại cho tôi một lỗi rất khó chịu: Can't reopen table

Một số nghiên cứu dẫn tôi đến báo cáo lỗi này nhưng những người ở MySQL dường như không quan tâm đến việc một tính năng cơ bản như vậy (sử dụng một bảng nhiều lần) không hoạt động với các bảng tạm thời. Tôi đang gặp phải rất nhiều vấn đề về khả năng mở rộng với vấn đề này.

Có giải pháp khả thi nào không yêu cầu tôi quản lý nhiều bảng tạm thời nhưng rất thực tế tiềm ẩn hoặc khiến tôi phải duy trì một bảng khổng lồ với tất cả dữ liệu trong đó?

Trân trọng, Kris

[bổ sung]

Câu trả lời GROUP_CONCAT không hoạt động trong trường hợp của tôi bởi vì điều kiện của tôi có nhiều cột theo thứ tự cụ thể, nó sẽ làm cho OR ngoài những gì tôi cần là AND. Tuy nhiên, nó đã giúp tôi giải quyết một vấn đề trước đó nên bây giờ bảng, tạm thời hay không, không còn cần thiết nữa. Chúng tôi chỉ nghĩ quá chung chung cho vấn đề của chúng tôi. Toàn bộ ứng dụng của bộ lọc hiện đã được đưa trở lại từ khoảng một phút đến dưới một phần tư giây.


2
Tôi đã gặp vấn đề tương tự khi sử dụng bảng tạm thời hai lần trong cùng một truy vấn sử dụng UNION.
Sebastián Grignoli

Câu trả lời:



121

Một giải pháp đơn giản là sao chép bảng tạm thời. Hoạt động tốt nếu bàn tương đối nhỏ, điều này thường xảy ra với các bàn tạm thời.


8
Thực sự nên là câu trả lời được chọn vì câu trả lời này giải đáp được vấn đề, mà không cần đi vòng quanh.
dyesdyes

3
bất kỳ lời khuyên về cách bạn sẽ sao chép bảng? (Ý tôi là một cách sao chép không lặp lại truy vấn)
Hernán Eche

16
Ngay cả khi bảng tạm thời lớn, bộ nhớ cache của mysql sẽ giúp bạn. Đối với việc sao chép từ bảng tạm thời này sang bảng tạm thời khác, một thao tác đơn giản là "TẠO BẢNG TẠM THỜI tmp2 CHỌN * TỪ tmp1".
AS7K

2
Nếu bạn sao chép nội dung tạm thời, đừng quên tạo chỉ mục, nếu không truy vấn của bạn có thể khá chậm.
gaborsch

1
@NgSekLong Vâng. Mọi lúc. Rõ ràng là nó phụ thuộc vào ứng dụng của bạn cho truy vấn nhưng tôi không thấy vấn đề hiệu suất "lớn" cho đến khi> 100.000. Trong một quy trình ETL, tôi sử dụng phương pháp này với một bảng 3,5 triệu. Tuy nhiên, tốc độ của ứng dụng đó không quan trọng.
Tanner Clark

49

Đúng, tài liệu MySQL nói: "Bạn không thể tham chiếu đến một TEMPORARYbảng nhiều hơn một lần trong cùng một truy vấn."

Đây là một truy vấn thay thế sẽ tìm các hàng giống nhau, mặc dù tất cả các điều kiện của các hàng phù hợp sẽ không nằm trong các cột riêng biệt, chúng sẽ nằm trong danh sách được phân tách bằng dấu phẩy.

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
Điều này thực sự không giải quyết được vấn đề của tôi trong tầm tay, nhưng nó đã cho phép tôi đơn giản hóa vấn đề gây ra nó, do đó loại bỏ sự cần thiết của bảng tạm thời. Cảm ơn!
Kris

6

Tôi đã giải quyết vấn đề này bằng cách tạo một bảng "tạm thời" vĩnh viễn và gắn SPID (xin lỗi, tôi đến từ SQL Server land) vào tên bảng, để tạo một tên bảng duy nhất. Sau đó, tạo các câu lệnh SQL động để tạo các truy vấn. Nếu bất cứ điều gì xấu xảy ra, bảng sẽ bị loại bỏ và được tạo lại.

Tôi hy vọng cho một lựa chọn tốt hơn. Thôi nào, MySQL Devs. Yêu cầu 'lỗi' / 'tính năng' đã được mở từ năm 2008! Có vẻ như tất cả các 'lỗi' đã gặp phải đều ở trên cùng một con thuyền.

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;

Hy vọng rằng bây giờ chúng ta đã có Oracle tiếp quản quyền thống trị, cô ấy có thể tạo ra một cú hích tốt cho MySQL.
Pacerier

2
thở dài Tôi nghi ngờ điều đó :(
tuần

3
Một tiếng thở dài . Tháng 7 năm 2016 và lỗi bảng tạm thời này vẫn chưa được sửa. Tôi có thể sẽ nghĩ ra một số loại số thứ tự được nối với tên bảng cố định (tôi đến từ vùng đất Oracle) để giải quyết vấn đề này.
TheWalkingData

Hattrick thở dài ... Nó có thể không bao giờ được sửa chữa, coi như năm 2019 của nó rồi.
Zimano

3

Cá nhân tôi chỉ muốn biến nó thành một bảng vĩnh viễn. Bạn có thể muốn tạo một cơ sở dữ liệu riêng cho các bảng này (có lẽ chúng sẽ cần tên duy nhất vì rất nhiều truy vấn này có thể được thực hiện cùng một lúc), cũng để cho phép đặt quyền một cách hợp lý (Bạn có thể đặt quyền trên cơ sở dữ liệu; bạn có thể ' t đặt quyền trên ký tự đại diện bảng).

Sau đó, bạn cũng cần một công việc dọn dẹp để thỉnh thoảng xóa những cái cũ (MySQL ghi nhớ một cách thuận tiện thời gian một bảng được tạo, vì vậy bạn có thể sử dụng nó để giải quyết khi cần dọn dẹp)


9
Các bảng tạm thời có ưu điểm là bạn có thể có nhiều truy vấn chạy đồng thời. Điều này là không thể với các bảng cố định.
Pacerier

Tôi nghĩ rằng "giải pháp" bảng vĩnh viễn không phải là một giải pháp. Nó giải quyết được vấn đề chắc chắn, nhưng không thực tế. Vì vậy, nhiều câu hỏi được đặt ra: Làm cách nào để tạo nhiều hơn một cùng một lúc? Bạn sẽ xử lý như thế nào với quy ước đặt tên và ghi đè lên các bảng được đặt tên giống nhau? Quá trình xóa bảng vĩnh viễn là gì? Nếu bạn có thể xây dựng một giải pháp khả thi bằng cách sử dụng các bảng cố định trong khi trả lời những câu hỏi này, thì tôi thật tuyệt!
Tanner Clark

0

Tôi đã có thể thay đổi truy vấn thành một bảng vĩnh viễn và điều này đã sửa lỗi cho tôi. (đã thay đổi cài đặt VLDB trong MicroStrategy, loại bảng tạm thời).


0

Bạn có thể giải quyết vấn đề này bằng cách tạo một bảng vĩnh viễn, bảng này bạn sẽ xóa sau đó hoặc chỉ tạo 2 bảng tạm thời riêng biệt với cùng một dữ liệu


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.