Mengatasi kesalahan MySQL "Can't reopen table"


91

Saat ini saya sibuk menerapkan jenis filter yang saya perlukan untuk membuat klausa INNER JOIN untuk setiap "tag" yang akan difilter.

Masalahnya adalah bahwa setelah sejumlah besar SQL, saya memiliki tabel yang berisi semua informasi yang saya butuhkan untuk membuat pilihan saya, tetapi saya membutuhkannya lagi untuk setiap INNER JOIN yang dihasilkan

Ini pada dasarnya terlihat seperti:

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

Ini berfungsi, tetapi saya lebih suka tabel "pencarian" bersifat sementara (bisa beberapa kali lipat lebih kecil jika itu bukan tabel normal) tetapi itu memberi saya kesalahan yang sangat mengganggu: Can't reopen table

Beberapa penelitian membawa saya ke laporan bug ini tetapi orang-orang di MySQL tampaknya tidak peduli bahwa fitur dasar seperti itu (menggunakan tabel lebih dari sekali) tidak berfungsi dengan tabel sementara. Saya mengalami banyak masalah skalabilitas dengan masalah ini.

Apakah ada solusi yang layak yang tidak mengharuskan saya untuk mengelola banyak tabel sementara tetapi sangat nyata atau membuat saya mempertahankan tabel besar dengan semua data di dalamnya?

Hormat kami, Kris

[tambahan]

Jawaban GROUP_CONCAT tidak berfungsi dalam situasi saya karena kondisi saya terdiri dari beberapa kolom dalam urutan tertentu, itu akan membuat OR dari apa yang saya perlukan menjadi AND. Namun, Itu membantu saya memecahkan masalah sebelumnya jadi sekarang tabel, temp atau tidak, tidak lagi diperlukan. Kami hanya berpikir terlalu umum untuk masalah kami. Seluruh aplikasi filter sekarang telah dibawa kembali dari sekitar satu menit menjadi kurang dari seperempat detik.


2
Saya memiliki masalah yang sama menggunakan tabel sementara dua kali dalam kueri yang sama menggunakan UNION.
Sebastián Grignoli

Jawaban:



126

Solusi sederhana adalah menduplikasi tabel sementara. Berfungsi dengan baik jika tabel relatif kecil, yang sering terjadi pada tabel sementara.


8
Seharusnya menjadi jawaban yang dipilih karena ini menjawab masalah, tanpa berputar-putar.
dyesdyes

4
ada saran tentang bagaimana Anda akan menggandakan tabel? (Maksud saya cara menyalin dan tidak mengulangi kueri)
Hernán Eche

17
Bahkan jika tabel temp besar, cache mysql akan membantu Anda. Sejauh menyalin dari satu tabel temp ke yang lain, sederhana "BUAT TABEL TEMPORER tmp2 SELECT * FROM tmp1" harus melakukannya.
AS7K

2
Jika Anda menyalin konten tabel waktu, jangan lupa untuk membuat indeks juga, jika tidak, kueri Anda mungkin akan lambat.
gaborsch

1
@Ng. Ya. Sepanjang waktu. Ini jelas tergantung pada aplikasi Anda untuk kueri, tetapi saya tidak melihat masalah kinerja "besar" hingga> 100.000. Dalam satu proses ETL, saya menggunakan metode ini dengan tabel 3,5mil. Kecepatan aplikasi itu tidak sepenting itu.
Tanner Clark

49

Benar, dokumen MySQL mengatakan: "Anda tidak dapat merujuk ke TEMPORARYtabel lebih dari sekali dalam kueri yang sama."

Berikut adalah kueri alternatif yang akan menemukan baris yang sama, meskipun semua kondisi baris yang cocok tidak akan berada di kolom terpisah, mereka akan berada dalam daftar yang dipisahkan koma.

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
Ini tidak benar-benar menyelesaikan masalah saya yang ada, tetapi itu memungkinkan saya untuk menyederhanakan masalah yang menyebabkannya, sehingga meniadakan kebutuhan akan temptable. Terima kasih!
Kris

6

Saya menyiasatinya dengan membuat tabel permanen "sementara" dan menambahkan SPID (maaf, saya dari tanah SQL Server) ke nama tabel, untuk membuat nama tabel yang unik. Kemudian membuat pernyataan SQL dinamis untuk membuat kueri. Jika terjadi sesuatu yang buruk, tabel akan dijatuhkan dan dibuat ulang.

Saya mengharapkan pilihan yang lebih baik. Ayo, MySQL Devs. 'Permintaan fitur' / 'bug' telah dibuka sejak 2008! Sepertinya semua 'serangga' yang ditemukan ada di perahu yang sama.

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;

Mudah-mudahan sekarang setelah Oracle mengambil alih pemerintahan, dia bisa memberikan dorongan yang baik pada MySQL.
Pacerier

2
sigh aku ragu :(
beeks

3
Desahan besar . Juli 2016, dan bug tabel temp ini masih belum diperbaiki. Saya mungkin akan menemukan beberapa jenis nomor urut yang digabungkan dengan nama tabel permanen (saya dari tanah Oracle) untuk menghindari masalah ini.
TheWalkingData

Hattrick menghela nafas ... Mungkin tidak akan pernah diperbaiki, mengingat ini sudah tahun 2019.
Zimano

3

Secara pribadi saya hanya akan menjadikannya meja permanen. Anda mungkin ingin membuat database terpisah untuk tabel-tabel ini (mungkin mereka membutuhkan nama yang unik karena banyak dari query ini dapat dilakukan sekaligus), juga untuk memungkinkan perizinan diatur secara bijaksana (Anda dapat mengatur izin pada database; Anda dapat ' t mengatur izin pada kartu bebas tabel).

Kemudian Anda juga memerlukan pekerjaan pembersihan untuk kadang-kadang menghapus yang lama (MySQL dengan nyaman mengingat saat tabel dibuat, jadi Anda bisa menggunakannya untuk bekerja saat pembersihan diperlukan)


9
Tabel sementara memiliki keuntungan ekstrem karena Anda dapat menjalankan beberapa kueri secara bersamaan. Ini tidak mungkin dilakukan dengan tabel permanen.
Pacerier

Saya pikir "solusi" tabel permanen bukanlah solusi. Ini memecahkan masalah dengan pasti, tetapi tidak praktis. Begitu banyak pertanyaan yang muncul: Bagaimana cara membuat lebih dari satu pada waktu yang sama? Bagaimana Anda menangani konvensi penamaan dan menimpa tabel dengan nama yang sama? Bagaimana proses menghapus tabel permanen? Jika Anda dapat menguraikan solusi yang layak menggunakan tabel permanen sambil menjawab pertanyaan-pertanyaan ini, saya setuju!
Tanner Clark

0

Saya dapat mengubah kueri menjadi tabel permanen dan ini memperbaikinya untuk saya. (mengubah pengaturan VLDB di MicroStrategy, tipe tabel sementara).


0

Anda bisa menyiasatinya dengan membuat tabel permanen, yang akan Anda hapus setelahnya, atau cukup buat 2 tabel temporer terpisah dengan data yang sama


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.