2009-04-25 3 views
2

로드하는 데 37 초가 걸리는 페이지가 있습니다. 로드하는 동안 MySQL의 CPU 사용량이 지붕을 통해 고정됩니다. 이 페이지의 코드를 작성하지 않았으므로 병목 현상의 원인이 쉽게 알 수 없으므로 다소 복잡합니다.PHP 페이지 최적화 : MySQL 병목 현상

(kcachegrind를 사용하여) 프로파일을 작성하고 페이지에서 대부분의 시간이 MySQL 쿼리를 수행하는 데 소요되는 시간의 90 %가 25 개의 서로 다른 mysql_query 호출에 사용되었습니다.

쿼리는의 형태를 취할하여 tag_id는 25 개 가지 통화의 각 변화와 함께 다음 각 쿼리는 좋은 측정을 위해 던진 몇 가지 이상 지연으로 완료하는 데 약 0.8 초가 걸립니다

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

... 따라서 페이지를 완전히로드하는 데 37 초가 소요됩니다.

제 질문은 문제의 원인이되는 중첩 된 선택을 사용하여 쿼리를 서식 지정하는 방식입니까? 또는 그것이 백만 가지 다른 것 중 하나 일 수 있습니까? 이 느린 문제를 해결하는 방법에 대한 조언을 주시면 감사하겠습니다.

쿼리에 EXPLAIN을 실행하면이 문제가 발생합니다 (하지만이 결과의 영향에 대해서는 명확하지 않습니다 ... 기본 키의 NULL이 나쁜 것처럼 보입니다. 반환되는 결과의 수가 나뿐만 아니라 결과의 소수만이 반환됩니다.)

 
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 
+1

tbl_news 및 tbl_tag_relations 테이블에 인덱스를 게시 할 수 있습니까? Explain에는 색인이 사용되지만 구성 요소는 사용되지 않습니다. 나는 이것이 '누락 된 인덱스'문제라고 생각한다. (25 개의 개별 쿼리를 실행해도 좋지 않다.) –

+0

당신이 맞았다. 조나단. 누락 된 색인 문제였습니다. Cletus는 어느 것을 제안하고 그의 제안은 10 배의 속도 향상을 가져왔다. – Stuart

답변

5

이 점을 Database Development Mistakes Made by AppDevelopers에 설명했습니다. 기본적으로 결합은 결합에 결합됩니다. IN은 집합체가 아니지만 동일한 원칙이 적용됩니다. 좋은 최적화 성능이 두 쿼리가 상당 할 것 :

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

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

내가 오라클과 SQL Server를 모두 수행하지만, MySQL이하지 않는 생각으로

. 두 번째 버전은 기본적으로 순간적입니다. 수십만 개의 행을 사용하여 컴퓨터에서 테스트를 수행하고 적절한 인덱스를 추가하여 1 초 미만의 성능으로 첫 번째 버전을 얻었습니다. 인덱스가있는 조인 버전은 기본적으로 순간적이지만 인덱스가 없어도 OK를 수행합니다.

그런데 내가 사용하는 구문은 조인을 할 때 선호해야하는 구문입니다. 그것들은 WHERE 절 (다른 것들도 제안했듯이)에 넣는 것보다 명확합니다. 그리고 위의 조건은 WHERE 조건에서 할 수없는 왼쪽 외부 조인을 사용하여 ANSI SQL 방식으로 특정 작업을 수행 할 수 있습니다.

그래서 나는 다음에 인덱스를 추가합니다 :

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

쿼리는 거의 즉시 실행됩니다 .

마지막으로 *를 사용하여 원하는 모든 열을 선택하지 마십시오. 명시 적으로 이름을 지정하십시오. 나중에 열을 추가하면 문제가 줄어 듭니다.

+0

+1 당신의 요점은 잘 받아 들여지지 만, 나는 cletus가 disabuse와 같은 단어를 사용한다고 생각하지 않습니다 :) – cgp

+0

위의 구문이 더 명확하고 반드시 명확하지는 않습니다. – cgp

+0

좋은 단어입니다. :) 아아서 내가 갔다가 체크하고 놀랍게도 MySQL은 조인 (나는 오라클이 그렇게 믿지 않는다)과 다르게 취급한다. 그에 따라 편집 됨. – cletus

2

정확하게 이해하면, 이것은 특정 태그 집합에 대한 뉴스 기사를 나열하는 것입니다. 모든

  1. 첫째, 당신이 정말로 적 SELECT *

  2. 두 번째로해야한다, 이것은 아마도
    이 따라서
    여러 쿼리의 오버 헤드 비용을 절감 단일 쿼리 내에서 수행 할 수 있습니다. 그것은 그렇게 가 대신 WHERE 조건으로 JOIN을 사용할 수 있습니다 IN를 사용하는 대신에 20

  3. 더 나은 방법의 단일 호출 내에서 검색 할 수있는 아주 사소한 데이터를 가져 오는 것처럼 보인다. IN을 사용하면 기본적으로 OR 문이 많이 사용됩니다.
  4. 귀하의 tbl_tag_relations 확실히 tag_id
+0

대답과 같은 진술을 쓰는 경우 왜 (몇 마디 이상) 이유를 설명하거나 더 나은 예제를 제공해야합니다. 모든 사람들이 이러한 사실을 알고있는 것은 아니며 OP는 분명히 데이터베이스에 대해 많이 알지 못한다고 분명히 말했습니다. –

+0

나는 예를 든다는 것과 솔직히, 나는 들판의 목록없이 할 수있다. – cgp

+0

SELECT *가 바람직하지 않은 특별한 이유가 있습니까? SELECT *는 테이블의 모든 단일 필드를 명시 적으로 나열하는 것과 어떻게 다른가요? – Calvin

1
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 

나는이 같은 결과를 얻을 수 있다고 생각하지만, 나는 100 % 확실하지 않다에 인덱스를 가져야한다. 때때로 단순히 결과를 제한하는 것이 도움이됩니다.

+0

tbl_tag_relations의 모든 필드에 기술적으로 같은 결과가 나오지는 않지만 어쨌든 두 필드 만있을 것입니다. –

+0

결과를 제한하면 결과가 줄어 듭니다. –

+0

lolz. 상상! ;) – cgp

3

SQL 쿼리 자체가 분명히 병목 현상입니다. 쿼리에는 코드의 IN (...) 부분 인 하위 쿼리가 있습니다. 이것은 기본적으로 한 번에 두 개의 쿼리를 실행합니다. JOIN (위의 d03boy에서 언급 한 것과 유사) 또는보다 대상이 지정된 SQL 쿼리를 사용하여 SQL 시간을 반으로 줄일 수 있습니다. 예를 들면 다음과 같습니다.

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

SQL 실행 속도를 높이려면 SELECT *를 사용하지 말고 원하는 정보 만 선택하십시오. 또한 마지막에 제한적인 진술을 넣는다.예 :

SELECT news_title, news_body 
... 
LIMIT 5; 

데이터베이스 스키마 자체를 살펴볼 수도 있습니다. 쿼리가 더 빨리 실행될 수 있도록 일반적으로 참조되는 모든 열에 대해 인덱싱을 수행해야합니다. 이 경우 news_id 및 tag_id 입력란을 확인하고 싶을 것입니다.

마지막으로, PHP 코드를 살펴보고 몇 가지 별도의 쿼리를 반복하는 대신 하나의 포괄적 인 SQL 쿼리를 만들 수 있는지 확인하십시오. 더 많은 코드를 게시하면 그 문제를 해결하는 데 도움이 될 수 있으며 게시 된 문제에 대한 가장 많은 시간을 절약 할 수 있습니다. :)

+1

이것은 문제가되지 않습니다. 검색어가 최적화되어 원본과 동일하게 실행됩니다. 두 번째 테이블이 전혀 선택되지 않았기 때문에 IN을 조인으로 다시 작성했습니다. 틀림없이 잘못된 것입니다. – cletus

+0

인덱스 Cletus가 트릭을 실제로 제안했습니다. 당신은 SELECT *에 대해 옳았습니다. 나는 그것을 처리 할 수있는 간단한 방법을 찾으려고 노력하고 있습니다. 그러나 코드는 꽤 거칠습니다. 그래서 그것을 변경하면 아마도 아직 상상조차하지 않은 쿼리에 영향을 미칠 것입니다 ... 나는해야 할 것입니다. 꼼꼼한. – Stuart

1

불행히도 MySQL은 귀하의 사례 쇼와 같이 상관없는 하위 쿼리를 사용하는 것이 좋지 않습니다. 기본적으로 외부 쿼리의 모든 행에 대해 내부 쿼리가 수행됩니다. 이것은 빨리 빠져 나올 것이다. 다른 사람들이 언급 한 것처럼 일반적인 오래된 조인으로 다시 작성하면 문제를 해결할 수 있지만 중복 행에 원치 않는 영향을 줄 수 있습니다.

예를 들어 원래 쿼리는 tbl_news 테이블의 각 규정 행 그러나이 쿼리에 대해 한 행을 반환 :

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) 

각 일치하는 태그에 대해 한 행을 반환합니다. 거기에 DISTINCT를 붙일 수 있습니다. DISTINCT는 데이터 세트의 크기에 따라 영향을 최소화해야합니다.

너무 심하게 트롤하지 말고 다른 대부분의 데이터베이스 (PostgreSQL, Firebird, Microsoft, Oracle, DB2 등)는 원본 쿼리를 효율적인 세미 조인으로 처리합니다. 개인적으로 하위 쿼리 구문이 훨씬 더 읽기 쉽고 쓰기 쉽다고 생각합니다. 특히 큰 쿼리의 경우 더욱 그렇습니다.

+0

나는 PostgreSQL을 사용해 왔지만 강력하지만 데이터베이스를 변경하는 것은 옵션이 아닙니다. – Stuart