2009-06-17 5 views
4

친구가 가장 많이 본 페이지를 표시 할 수있는 기능을 제공하려고합니다. 내 친구 테이블에는 5.7M 행이 있고보기 테이블에는 5.3M 행이 있습니다. 지금은이 두 테이블에 대한 쿼리를 실행하고 가장 많이 본 페이지 ID를 사람의 친구가 찾고자합니다. 여기 두 개의 큰 테이블에서 간단한 쿼리 최적화

내가 지금 가지고있는 쿼리의 :

SELECT page_id 
FROM `views` INNER JOIN `friendships` ON friendships.receiver_id = views.user_id 
WHERE (`friendships`.`creator_id` = 143416) 
GROUP BY page_id 
ORDER BY count(views.user_id) desc 
LIMIT 20 

을 그리고 여기에 외모를 설명하는 방법은 다음과 같습니다

+----+-------------+-------------+------+-----------------------------------------+---------------------------------+---------+-----------------------------------------+------+----------------------------------------------+ 
| id | select_type | table  | type | possible_keys       | key        | key_len | ref          | rows | Extra          | 
+----+-------------+-------------+------+-----------------------------------------+---------------------------------+---------+-----------------------------------------+------+----------------------------------------------+ 
| 1 | SIMPLE  | friendships | ref | PRIMARY,index_friendships_on_creator_id | index_friendships_on_creator_id | 4  | const         | 271 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | views  | ref | PRIMARY         | PRIMARY       | 4  | friendships.receiver_id     | 11 | Using index         | 
+----+-------------+-------------+------+-----------------------------------------+---------------------------------+---------+-----------------------------------------+------+----------------------------------------------+ 

견해 테이블 ​​(USER_ID, 페이지 ID)의 기본 키가를, 그리고 당신 이것이 사용되고있는 것을 볼 수 있습니다. 우정 테이블에는 (receiver_id, creator_id)의 기본 키와 (creator_id)의 보조 색인이 있습니다.

group by 및 limit없이이 쿼리를 실행하면이 특정 사용자에 대해 약 25,000 개의 행이 발생합니다. 이는 일반적입니다.

가장 최근의 실제 실행에서이 쿼리는 7 초가 지나서 실행되었는데, 이는 웹 앱의 적절한 응답을하기에는 너무 길다.

두 번째 색인을 (creator_id, receiver_id)로 조정해야하는지 궁금한 점이 하나 있습니다. 나는 그것이 성능 향상을 많이 줄 것이라고 확신하지 못합니다. 나는이 질문에 대한 대답에 따라 오늘 그것을 시도 할 것입니다.

번개를 빨리 줄이기 위해 쿼리를 다시 작성할 수있는 방법을 볼 수 있습니까?

업데이트 : 더 많은 테스트를해야하지만 DB에서 그룹화 및 정렬을 수행하지 않으면 내 불쾌한 쿼리가 더 잘 나타납니다.하지만 나중에 루비에서 수행하십시오. 전반적인 시간은 훨씬 짧습니다 - 약 80 % 정도는 보인다. 어쩌면 내 초기 테스트에 결함이있는 것 같았지만 이는 분명히 더 많은 조사가 필요합니다. 그것이 사실이라면 - wtf가 MySQL에서하고있는 것입니까?

+0

두 표를 설명해 주시겠습니까? –

+0

두 테이블 모두 거의 볼 수 있습니다. Friendship은 receiver_id (int)와 creator_id (int)를 가지고 있고, 다른 자동 증가 ID 필드를 보조 키로 가지고 있습니다 (레일스 + memcached는 적합하지 않습니다). 뷰에는 user_id (int), page_id (bigint) 및 자동 증가 ID 필드가 있습니다. –

+0

friendships.receiver_id 및 views.user_id가 모두 색인 생성되어 있다고 가정합니다. 그리고 page_id에 대한 bigint? int는 최대 43 억 개의 값을 저장할 수 있습니다 (성능 병목 현상에 대한 브레인 스토밍). –

답변

1

필자가 아는 바로는 "번개처럼 빠른"쿼리를 만드는 가장 좋은 방법은 제작자별로 페이지 당 친구 페이지 뷰를 추적하는 요약 테이블을 만드는 것입니다.

아마도 트리거를 최신으로 유지하고 싶을 것입니다. 그런 다음 집계가 이미 완료되어 가장 많이 본 페이지를 가져 오는 간단한 쿼리입니다. 요약 테이블에 적절한 인덱스가 있는지 확인하여 데이터베이스가 가장 많이 볼 수 있도록 정렬 할 필요가 없도록 할 수 있습니다.

요약 테이블은 읽기 전용 환경에서 집계 유형 쿼리의 우수한 성능을 유지 관리하는 데 중요한 요소입니다. 업데이트가 발생하고 (빈번하지 않은) 쿼리가 자주 수행 될 필요가없는 경우 작업을 미리 수행하십시오.

통계가 완벽하지 않아도 좋고 쓰기가 실제로 빈번한 경우 (페이지 뷰와 같은 경우), 메모리에서 뷰를 일괄 처리하고 백그라운드에서 처리 할 수 ​​있으므로 친구들이 페이지를 볼 때 요약 테이블을 최신 상태로 유지할 필요가 없다는 사실을 알았습니다. 또한이 솔루션은 데이터베이스의 경합을 줄입니다 (요약 테이블을 업데이트하는 프로세스가 적음).

+0

제안에 감사드립니다. Nathan. 쓰기는 꽤 빈번합니다. 읽기 당 약 100 번입니다. 제한된 세트 만 추적 할 수있는 몇 가지 방법이있을 수 있습니다. 나는 그것을 약간 생각할 것이다. –

+0

여러 스레드에서 쓰기가 발생합니까? 그렇다면, 나는 메모리에서 그것들을 집계하고, 모든 쓰레드를 수행하는 단일 스레드/연결을 갖는 것을 확실히 고려할 것이다. 그렇다면 같은 테이블에 글을 쓰는 다툼이 없을 것입니다. – nathan

0

이 표를 비정규 화해야합니다. 사용자 ID와 조회 한 모든 페이지의 정확한 수를 유지하는 별도의 테이블을 만드는 경우 쿼리가 훨씬 더 단순 해집니다.

'views'테이블에 삽입이 발생할 때마다 'views_summary'테이블을 업데이트하는 views 테이블의 트리거를 사용하여이 테이블을 쉽게 유지 관리 할 수 ​​있습니다.

심지어 실제 관계를 보면 더욱이 비정규, 아니면 그냥이 도움이

희망,

에버트

+0

안녕하세요, 귀하의 답변 주셔서 감사합니다. 사용자 당 각 페이지의 조회수를 유지하는 것보다 조금 까다 롭습니다. 그것이 까다로운 이유는 총 사용자 수가 아닌 페이지를 본 친구의 수를 계산하기 때문입니다. –

+0

네, 이것이 최종 해결책이 아니라는 것을 알고 있습니다. 그러나 이것은 이미 귀하의 질의를 매우 단순화시킬 것입니다. 이 시점에서 사용자 및 최상위 페이지의 캐시 된 목록을 관리하고 데이터베이스를 전혀 건드리지 않고 프론트 엔드 계층 내에서 정렬 할 수 있습니다. – Evert

0

귀하의 인덱스가 올바른 보는 사람마다 상단 X 페이지를 유지할 수 있습니다 friendship에 매우 큰 행이있는 경우 색인을 모두 읽지 않으려면 (creator_id, receiver_id)에 대한 색인을 원할 수 있습니다.

그러나 여기에 뭔가가 없습니다. 왜 271 행의 파일을 열어 둡니까? MySQL에 tmp_table_sizemax_heap_table_size에 대해 최소한 몇 메가 바이트가 있는지 확인하십시오. 그것은 GROUP BY를 더 빨리 만들어야합니다.

sort_buffer도 정상 값을 가져야합니다.

+0

tmp_table_size는 33MB, max_heap_table_size는 16MB, sort_buffer는 2MB입니다. 나는 옳지 않은 것에 동의합니다. Ruby는 그룹을 처리하고 현재 mysql보다 훨씬 빠르게 정렬합니다. –

+0

그래서 25K 정수이면 충분합니다 (tmp_table_size, max_heap_table_size) 16MB의 효과적인 tmp_table_size입니다. 모든 데이터를 Ruby로 보내면 좋을 것입니다. GROUP BY 만 사용해 보셨습니까? –

+0

정수가 640 바이트를 차지합니까? 당신은 250k 정수를 의미하지 않습니까? tmp_table_size를 크게 늘려보고 영향을 미치는지 살펴 보겠습니다. –