MySQL darboğaz: Bir PHP sayfası optimize

5 Cevap php

Ben yüklemek için 37 saniye alıyor bir sayfa var. Yükleme işlemi devam ederken bu çatıdan MySQL CPU kullanımını mandal. Bu sayfa için kod yazmadım ve o kadar darboğazı nedeni bana aşikar değil oldukça kıvrık.

Ben (KCacheGrind kullanarak) profilli ve sayfadaki zaman toplu MySQL sorguları (90 zaman% 25 farklı mysql_query çağrılarda harcanan) yaparak geçirmiş olduğunu bulmak.

Sorguları tag_id 25 farklı aramaların her biri değişen aşağıdaki formu alır:

SELECT * FROM tbl_news WHERE news_id
 IN (select news_id from
 tbl_tag_relations WHERE tag_id = 20)

Her sorgu böylece 37 saniye tamamen sayfasını yüklemek için ... iyi ölçmek için atılmış bir kaç uzun gecikmeler ile tamamlamak için yaklaşık 0.8 saniye alıyor.

Benim soru, bu soruna neden olan seçmek sorgu İç içe geçmiş ile biçimlendirilmiş yoludur? Yoksa bir milyon diğer şeylerin herhangi biri olabilir? Bu yavaşlık mücadele yaklaşım konusunda herhangi bir tavsiye takdir edilmektedir.

Sorguya EXPLAIN Koşu bana bu veriyor (ama bu sonuçların etkisi konusunda net değilim kötü olacak gibi ... birincil anahtar NULL evet? Döndürülen sonuç sayısı bana yüksek görünüyor gibi görünüyor sonuç sadece bir avuç) sonunda döndürülür:

1    PRIMARY	 tbl_news	ALL	NULL	NULL	NULL	NULL	1318	Using where
2   DEPENDENT SUBQUERY	tbl_tag_relations	ref	FK_tbl_tag_tags_1	FK_tbl_tag_tags_1	4	const	179	Using where

5 Cevap

Ben'e Database Development Mistakes Made by AppDevelopers bu noktayı ele. Temel olarak, iyilik yığınlaşma katılır. IN gibi agregasyon değil, aynı prensip geçerlidir. İyi optimize performans bu iki sorguları eşdeğer yapacak:

SELECT * FROM tbl_news WHERE news_id
 IN (select news_id from
 tbl_tag_relations WHERE tag_id = 20)

ve

SELECT tn.*
FROM tbl_news tn
JOIN tbl_tag_relations ttr ON ttr.news_id = tn.news_id
WHERE ttr.tag_id = 20

as I believe Oracle ve SQL Server both do but MySQL doesn't. The second version is basically instantaneous. With hundreds of thousves of rows I did a test on my machine ve got the first version to sub-second performance by adding appropriate indexes. The join version with indexes is basically instantaneous but even without indexes performs OK.

By the way, the above syntax I use is the one you should prefer for doing joins. It's clearer than putting them in the WHERE clause (as others have suggested) ve the above can do certain things in an ANSI SQL way with left outer joins that WHERE conditions can't.

Yani aşağıdaki dizinleri eklemek istiyorum:

  • tbl_news (news_id)
  • tbl_tag_relations (news_id)
  • tbl_tag_relations (tag_id)

ve the query will execute almost instantaneously.

Son olarak, istediğiniz tüm sütunları seçmek için * kullanmayın. Açıkça onları Ad. Daha sonra sütunlar eklemek gibi daha az sorun içine alırsınız.

SQL Query kendisi kesinlikle tıkanıklık olduğunu. Sorgu kod kısmı IN (...) olduğu içinde bir alt sorgu vardır. Bu aslında aynı anda iki sorgu çalışıyor. Büyük olasılıkla (veya daha fazla!) Yarıya SQL kez JOIN ile (benzer ne d03boy yukarıda söz) veya daha fazla hedef SQL sorgusu. Bir örnek olabilir:

SELECT * 
FROM tbl_news, tbl_tag_relations 
WHERE tbl_tag_relations.tag_id = 20 AND
tbl_news.news_id = tbl_tag_relations.news_id

Hızlı da SELECT * kullanarak önlemek için denemek için istediğiniz SQL çalışmasına yardımcı ve sadece ihtiyacınız olan bilgileri seçmek için; Ayrıca sonunda bir sınırlayıcı açıklama koydu. örneğin:

SELECT news_title, news_body 
... 
LIMIT 5;

Ayrıca veritabanı şeması kendi içine bakmak isteyecektir. Sorguları hızlı çalışacaktır böylece yaygın sütunları adlandırılan tüm endeksleme emin olun. Bu durumda, muhtemelen news_id ve tag_id alanları kontrol etmek istiyorum.

Son olarak, PHP kodu bir göz atın ve yerine birkaç ayrı sorguları yineleme bir tek şeyi kapsayan bir SQL sorgusu yapabilirsiniz görmek isteyecektir. Daha fazla kod sonrası eğer biz yardımcı olabilir, ve muhtemelen yayınlanmıştır sorun için tek büyük zaman tasarrufu olacak. :)

Eğer doğru anlamak, bu sadece etiketleri belirli bir kümesi için haberleri listeleme.

  1. First of all, you really shouldn't ever SELECT *

  2. Second, this can probably be
    accomplished within a single query, thus reducing the overhead cost of
    multiple queries. It seems like it is getting fairly trivial data so it could be retrieved within a single call instead of 20.

  3. Kullanarak Daha iyi bir yaklaşım IN yerine, WHERE koşulu ile bir JOIN kullanmak olabilir. Bir kullanırken IN bu temelde OR tabloların bir sürü olacaktır.
  4. Sizin tbl_tag_relations Kesinlikle tag_id üzerinde bir dizin olmalıdır
select * 
 from tbl_news, tbl_tag_relations 
 where 
      tbl_tag_relations.tag_id = 20 and 
      tbl_news.news_id = tbl_tag_relations.news_id 
 limit 20

Ben bu aynı sonuçları verir düşünüyorum, ama ben% 100 emin değilim. Bazen sadece sonuçları yardımcı olur sınırlayıcı.

Ne yazık ki MySQL için dava gösterileri gibi ilintisiz sorgular ile çok iyi yapmaz. Plan temelde dış sorguda her satır için, iç sorgu yapılacağı söylüyor. Bu hızla elden çıkmak olacaktır. Diğerleri de söylediğim gibi katılmak, bir düz eski olarak yeniden soruna edecek ama sonra istenmeyen yinelenen satırları etkileyebilir neden olabilir.

Örneğin özgün sorgu tbl_news tablodaki her satırda eleme ancak bu sorgu için 1 satır dönecekti:

SELECT news_id, name, blah
FROM tbl_news n
JOIN tbl_tag_relations r ON r.news_id = n.news_id
WHERE r.tag_id IN (20,21,22)

her eşleşen etiket için 1 satır dönecekti. Sadece veri kümesi boyutuna bağlı olarak bir minimum performans etkisi olması gereken orada DISTINCT sopa olabilir.

Çok kötü troll, ancak diğer veritabanları (PostgreSQL, Firebird, Microsoft, Oracle, DB2, vb) gibi özgün sorguyu işlemek olmaz verimli bir yarı-katılın. Şahsen ben sorgu sözdizimi özellikle büyük sorgular için, çok daha okunabilir ve yazmak daha kolay bulabilirsiniz.