2015-08-21 16 views
7

PHP kullanarak MySQL DB'den bazı verileri seçmem gerekiyor. İyi bir sunucuda çalışmak için 5 dakika süren tek bir MySQL sorgusunda yapılabilir (10 Mio satırında daha fazla tablodaki çoklu JOIN'ler).Yavaş MySQL Sorgu - Bir PHP dizisinde verileri önbelleğe alın?

oldukça MySQL daha PHP sorguyu bölmek ve bazı döngüler kullanmak için daha iyi bir uygulama olup olmadığını merak ediyorum. Ayrıca, bir tablodaki tüm e-postaları bir dizideki 150 000 satır ile sorgulamak ve daha sonra binlerce MySQL SELECT yapmak yerine diziyi kontrol etmek daha iyi olur.

SELECT count(contacted_emails.id), contacted_emails.email 
FROM contacted_emails 
LEFT OUTER JOIN blacklist ON contacted_emails.email = blacklist.email 
LEFT OUTER JOIN submission_authors ON contacted_emails.email = submission_authors.email 
LEFT OUTER JOIN users ON contacted_emails.email = users.email 
GROUP BY contacted_emails.email 
HAVING count(contacted_emails.id) > 3 

AÇIKLAYINIZ döner: Burada

Sorgu 4 tablolarda EXPLAIN

endeksleri şunlardır:

contacted_emails: id, blacklist_section_id, journal_id and mail 
blacklist: id, email and name 
submission_authors: id, hash_key and email 
users: id, email, firstname, lastname, editor_id, title_id, country_id, workplace_id 

oluşturulur

tablo contacted_emails

jobtype_id gibi:

CREATE TABLE contacted_emails ( 
    id int(10) unsigned NOT NULL AUTO_INCREMENT, 
    email varchar(150) COLLATE utf8_unicode_ci NOT NULL, 
    contacted_at datetime NOT NULL, 
    created_at datetime NOT NULL, 
    blacklist_section_id int(11) unsigned NOT NULL, 
    journal_id int(10) DEFAULT NULL, 
    PRIMARY KEY (id), 
    KEY blacklist_section_id (blacklist_section_id), 
    KEY journal_id (journal_id), 
    KEY email (email)) 
ENGINE=InnoDB AUTO_INCREMENT=4491706 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 
+4

Genel kural olarak, SQL her zaman PHP'den daha hızlı olacaktır. Sorgunuz 5 dakika sürüyorsa, milyonlarca kayıt ve birden çok katılımla bile, bir alt-optimal sözdizimi veya eksik bir dizin var. Daha fazla optimizasyon için sorgunuzun yürütme planını kontrol etmek için bir EXPLAIN yapmalısınız. –

+1

Sorgunuzu ve EXPLAIN çıktısını gösteren daha spesifik bir soruyu yeniden yazmalı ve birisinin düzeltip düzeltemeyeceğini görün. –

+0

@StevenMoseley, teşekkürler. Lütfen düzenlenmiş sorudaki sorguya bakın. Büyük tablo 10 Mio satırı olan contacted_emails'tir. Hangi e-postaların contacted_postalarda olduğunu, kullanıcılara değil de submission_authors içinde olduğunu bilmem ve 3 kattan fazla iletişim kurdum. –

cevap

0

veren bkz senin recommandations, bu çözümü tercih edilmiştir: Bu an için gayet hangi çalıştırmak için 10sn sürüyor

SELECT ce.email, ce.number_of_contacts 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING number_of_contacts > 3 
) AS ce 
NATURAL LEFT JOIN blacklist AS bl 
NATURAL LEFT JOIN submission_authors AS sa 
NATURAL LEFT JOIN users AS u 
WHERE bl.email IS NULL AND sa.email IS NULL AND u.email IS NULL 

. Veritabanında daha fazla veriye sahip olacağım, geçici bir tablo oluşturacağım başka bir çözüm düşünmem gerekecek.

Sonuç olarak, tüm tabloyu php dizisi olarak yüklemek, mysql sorguları yapmak için performans açısından iyi değildir.

+1

'COUNT (id)' ile 'COUNT (*)' arasında değiştirmeyi denediniz mi? Performansı arttırıp arttırmadığını bilmek isterdim. Ayrıca sayımı zaten yaptığınız gibi alt sorguda 'HAVING number_of_contacts> 3' kullanabilirsiniz. – Arth

+0

@Arth, 'COUNT (id)' ile 'COUNT (*)' arasında bir değişiklik yapmanın performansı üzerinde hiçbir etkisi yoktur. Ancak, 'HAVING COUNT (id)> 3' değerinin 'HAVALANDIRMA number_of_contacts> 3 'olarak değiştirilmesi, performansı iyileştirdi (20 sn'den 10 sn'ye). Cevabı ben düzenledim, çok teşekkürler. –

2

sorgunun açısından düşüncelerin Birkaç, daha hızlı sen

count(*) row_count 

ve değiştirirseniz bulmak Zira bu

row_count > 3 

contacted_emails.email den memnun olabilir HAVING

için contacted_emails.id almak için satır erişmek zorunda kalmadan dizin. Her iki alan da NOT NULL ve contacted_emails taban tablosudur, bu aynı mantık olmalıdır.

daha fazla veri toplamak olarak bu sorgu sadece uzatacaktır olarak sana (muhtemelen bazı zaman birimi başına) sayılarını depolamak özet tablosunu öneririz. Bu, bir cronjob ile periyodik olarak veya tetikleyiciler ve/veya uygulama mantığı ile anında güncellenebilir. Eğer created_at bir zaman birimi başına seçeneğini kullanın ve/veya cron son güncelleme saklıyorsanız

, içeri çekerek ve en son verileri ekleyerek canlı sonuçlar elde etmek mümkün olmalıdır.

Herhangi önbellek çözümü canlı kalmayı zaten ayarlanması gerekir ve tam sorgu verileri güncellenen/temizlenir her zaman çalıştırın.

Açıklamalarda önerildiği gibi, veritabanı büyük miktarlarda veri toplamak için oluşturulmuştur .. PHP değildir.

+0

HAVING ile eposta sayıyorsanız, oldukça yavaş olan DISTINCT kullanmanız gerekir. – Mihai

+0

@Mihai Yep, DISTINCT hakkında tamamen doğru olduğunuzdan emin değilim, ancak gruplamayı yanlış okudum, bu öneriyi çıkaracağım – Arth

2

Muhtemelen, her ekleme işleminde tetiklenen e-postalar tablonuzda tetiklenen bir Özet tablosunda en iyisi olabilirsiniz. Bu Özet tablosunun e-posta adresi ve bir sayı sütunu olmalıdır. Temas edilen tabloya her ekleme, sayımı güncelleyin. Özet tablosunda sayım sütununda bir dizin var. Ardından, doğrudan THAT'dan sorgulayabilir, söz konusu e-posta hesabını alabilir, daha sonra çekilmesi gereken her şeyin geri kalanını almak için daha sonra katılın.

+0

Bu makul bir çözüm değil. Verileri bir araya getirmemiz gereken her zaman, "sayım" tabloları oluşturmalıyız, programcıların emeceği işlerimiz. Sayımlar senkronizasyondan çıkar. Pazarlama, ortalamalar veya ay başına ya da her neyse sayım istediğine karar verirdi. O zaman tüm hacklenmiş programlamayı tekrar yapmak zorundayız. İşte bu yüzden SQL var - bu karmaşık işleri anında yapmak için, toplanmış rakamların listesini İHTİYACINIZ. –

+1

@StevenMoseley, Saygılarımla katılmıyorum. Bazı durumlarda, söz konusu sitelerin içeriğine veya genel olarak veri madenciliğine dayanır. Tetikleyiciler, topladıkları, roll-up'ları, vb. Güncelleştirmek için devreye sokulursa, bunlardan temel olarak WULD sorgulaması daha hızlı olacaktır. Tablo, ONCE oluşturulur ve OTHER tablosundaki tetikleyiciler sizin için ekleme/güncelleştirme yapar. Birincil kriterler oluşturulduktan sonra, ayrıntılara girme daha fazla ham veriye ulaşırdı. – DRapp

3

Dizinleriniz iyi görünüyor.

Performans sorunları, tüm satırları JOIN olduğunuzdan ve ardından HAVING kullanarak filtreleyerek geliyor gibi görünüyor.

Bu muhtemelen daha iyi yerine çalışacak:

SELECT * 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING COUNT(id) > 3 
) AS ce 
LEFT OUTER JOIN blacklist AS bl ON ce.email = bl.email 
LEFT OUTER JOIN submission_authors AS sa ON ce.email = sa.email 
LEFT OUTER JOIN users AS u ON ce.email = u.email 
/* EDIT: Exclude-join clause added based on comments below */ 
WHERE bl.email IS NULL 
    AND sa.email IS NULL 
    AND u.email IS NULL 

Burada önemli ölçüde daha uygunudur JOIN s, önce ayarlanmış başlangıçtaki GROUP ed verilerini sınırlama ediyoruz.

rağmen orijinal sorgu bağlamı göz önüne alındığında, aşağıda muhtemelen daha az havai ile tam olarak aynı sonuçları dönecekti böylece hiç kullanılacak gibi görünüyor dom't LEFT OUTER JOIN tablolar:

SELECT email, COUNT(id) AS number_of_contacts 
FROM contacted_emails 
GROUP BY email 
HAVING count(id) > 3 

tam olarak ne JOIN ed tablolarının amacı nedir? LEFT JOIN, verilerin herhangi birini azaltmalarını önler ve yalnızca contacted_emails arasındaki toplam verilere bakarsınız. Bunun yerine INNER JOIN mu demek istediniz?


DÜZENLEME: Birleştirme noktalarının varolan tablolarınızdaki e-postaları dışlamak olduğunu belirttiniz. İlk dış sorgumu, düzgün bir hariç tutma birleştirme yapmak için değiştirdim (bu, asıl posta kodunuzdaki bir hataydı). Ne burada ne işim bir alt sorguda varolan e-postaları toplama ve yapıyor

SELECT 
FROM contacted_emails 
LEFT JOIN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) AS existing ON contacted_emails.email = existing.email 
WHERE existing.email IS NULL 
GROUP BY contacted_emails.email 
HAVING COUNT(id) > 3 

tek o türetilmiş masaya katılmak hariç:

İşte sizin için iyi performans başka bir olası seçenek.

bu WHERE deyimindeki olmayan ilişkili alt sorgu gibidir çalışabilirim bir başka yolu:

SELECT 
FROM contacted_emails 
WHERE email NOT IN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) 
GROUP BY email 
HAVING COUNT(id) > 3 

hepsini deneyin ve ardından MySQL

en iyi yürütme planını
+0

Merhaba Steven, cevabınız için teşekkür ederim. “SOL DIŞ JOIN”, “USERS”, “submission_authors” ve “blacklist” tablolarında bulunan e-postaları hariç tutmak için kullanılır. Hariç tutulacak e-postalara ihtiyacım var. –

+0

@ Miloš - Bu durumda, dışlamak için IS NULL filtresi kullanmalısınız. Cevabımı düzenleme. –