2011-02-28 3 views
2

나는 오랜 시간이 걸리는 쿼리를 가지고 있으며이를 최적화하려고합니다. 나는 그것을하는 가장 효율적인 방법을 찾고 있습니다.쿼리 최적화 - 필드 또는 다른 테이블 사용

나는 PostgreSQL DB가있는 Hibernate/JPA에서 일하고 있지만 모든 솔루션은 일반적인 JPA 중 하나 여야합니다.

용어

  • 사용자 : 시스템의 사용자입니다.
  • 친구 : 사용자의 친구. 사용자에게는 N 명의 친구가 있습니다.
  • 세션 : 시스템 사용 세션. 열거 나 닫을 수 있습니다.
  • 컨텍스트 : 세션 컨텍스트. 사용자는 주어진 시간에 컨텍스트 당 하나의 열린 세션을 가질 수 있으며 컨텍스트 당 많은 과거의 닫힌 세션을 가질 수 있습니다.

쿼리 나 사용자 이름 주어진 다음 나에게주는 쿼리 구현해야

:

  • 해당 사용자 각각에 대해
  • 모든 친구를 가져 오기를 친구 :
    • 친구가 열려있는 세션이있는 경우 모든 컨텍스트에 대해 열려있는 모든 세션을 가져옵니다.
    • 그렇지 않으면 모든 컨텍스트에서 친구의 최신 세션을 가져옵니다. 우정 그래서 난 어떤 경우에 하나 개의 큰 쿼리로 그것을 포함하지 수있는 다른 DB에 저장되어

참고. B, C, D :

사용자 A가 세 친구가 있습니다. 1과 2의 두 가지 상황이 있습니다.친구는 다음과 같은 데이터를 가지고 :

(아래 서식은 세션 ID입니다 - 사용자, 컨텍스트)

  • 1 - B, 1 : 오픈 세션
  • 2 - B, 2 : 시작 비공개 회의 2 월 27 일에
  • 3 - B, 2 : 시작 폐쇄 세션 2 월 26
  • 4 - C : 1, 2 월 27
  • 5 시작 폐쇄 세션 - C : 1 폐쇄 세션 그 2 월 26 일에 시작했습니다.
  • 6 - C, 2 : 2 월 26
  • 7 시작 폐쇄 세션 - C, 2 : 폐쇄 세션 2 월 25
  • 8 시작 - D, 1 개 세션
  • - 9 D, 2 : 오픈 세션

쿼리는 저를 얻어야한다 : B : 세션 1 (열려있는 모든 세션) C : 세션 4 (최신 비공개 회의) D : 세션 8,9 (열려있는 모든 세션)

현재 상태

내 쿼리는 세 단계로 작동합니다있을 경우

  1. 이 친구
  2. 을 열려있는 모든 세션을 가져 오기 :
    1. 각 친구를 위해 사용자
    2. 의 모든 친구를 가져옵니다 열려있는 모든 세션은,
    3. 이 친구의 최신 세션을 가져 열려있는 모든 세션을 반환 세션을 반환

분명히 이것은 많은 쿼리입니다. 처음에는 위의 2 단계를 수행하여 단일 쿼리으로 변환하겠습니다. 내 우려는 두 번째 쿼리와 관련이 있습니다. 문제는 그것이 더 최적화되도록하는 방법입니다. 문제는, 따라서 고쳐 할 수 있습니다

"N 친구 ID의 집합을 감안할 때, 열려있는 모든 세션이나 모든 친구를위한 최신 세션을 얻을."

제안 된 솔루션을

우리가 함께했다 기본적으로 두 가지 해결책이 있고 우리는 더 나은 것 무엇을 고민하고 있습니다.

테이블 솔루션은 사용자, 컨텍스트 및 최신 세션간에 상관 관계가있는 새 테이블을 유지한다고 말합니다.이 솔루션의 의미는 다음과 같습니다

  • 사용자
  • 상황
  • 최신 세션 ID
  • :

    • 이 표는 이러한 열을 것 "최신 세션"
    • 의 새로운 법인 & 테이블을 만듭니다
    의 표는 게시물에 세션 개체가 업데이트됩니다
  • 는 지속
  • 새로 P 있도록 ersisted 세션은이 테이블을 자동으로 업데이트합니다.
  • 새 쿼리는이 테이블에서 사용자의 모든 친구에 대한 모든 레코드를 가져와 최종 결과를 만들기 위해 작업합니다.

열 솔루션은 세션 테이블에 "최신"플래그 열을 유지한다고 말합니다. 이 솔루션의 의미는 다음과 같습니다

  • 열이 우편으로 설정됩니다 세션 엔티티의 지속 최신 (부울)에 대한 새 필드를 만들기 전 "최신"세션 수 있도록 더 이상 최신 세션이 아니며 새로운 세션이 최신 세션이됩니다.
  • 새로운 쿼리는 최종 결과를 생성하는 그들에 원래 세션 테이블과 직장에서 사용자의 모든 친구 (명령문의 조건에 새 열을 포함하여) 모든 최신 기록을 가져옵니다.

각각에 장단점이 있으며 아직 우승자가 없습니다. 분명히 우리가 고려하지 않은 다른 해결책이있을 수 있습니다. 제가보기를 원하는 것은 위에 열거 된 것 중 무엇이 더 좋고, 왜, 또는 더 나은 접근 방법인지에 있습니다.

+0

우정을위한 DB가 다른 이유는 무엇입니까? 정말 다른 DB 또는 다른 스키마입니까? – Unreason

+0

기능이있는보기를 사용하는 방법은 어떻습니까? 가능한 경우 캐싱이 큰 도움이 될 수 있습니다. –

+0

필자가 이해하는 한, 뷰는 복잡한 쿼리를 어떤 방식 으로든 실행하기 때문에 DB 업데이트 중에 더 많은 관리를 수행하여 쿼리의 성능을 향상시켜 이러한 상황을 피하려고합니다. –

답변

1

두 솔루션의 차이는 제한적이어야합니다. 표 용액은 활동에 따라보다 청결 할 수 있습니다.

그러나, (이론에 따르면) '당신이 그것을 잘못하고있다'참고 않습니다.

RDBMS에 응용 프로그램 디자인 원칙은 명확하게 당신이 당신의 쿼리를 실행하는 방법을 지정하려고해서는 안한다고하지만, 어떤 데이터를 당신이 원하는. 데이터베이스가 솔루션에 가장 적합한 경로를 찾습니다 (RDBMS가 데이터에 가장 가까이 위치하므로 아키텍처에 따라 네트워크 왕복, 스토리지 왕복 등을 줄일 수 있습니다.) 확장 성은 여기에 심각하게 불구가 될 수 있습니다. 괜찮은 스트레스 테스트를 수행하지 않으며, RDBMS는 스캔이나 검색이 더 효과적 일지 그리고 최적으로 조인을 수행하는 방법을 알고 있는지를 결정하는 내부 통계 및 인덱스에 대해 알고 있습니다.

실제로, 우정을위한 다른 데이터베이스가 왜 필요합니까? (정말 다른 db 또는 동일한 db에 대한 다른 스키마입니까?).다음 가장 중요한 요소는 당신이 정말로 길을 갈려면

또한, 당신은 (는 RDBMS를 사용하지 않도록하는 것이 최적의 실행 계획을 확인합니다) 그것을 할 수 있습니다

  • 인덱스 (주문의 성능에 영향을 미칩니다
  • 사용 패턴) 크기의 (인덱스에 select의 성능을 향상시킬 수 있지만, 너무 많은 인덱스 업데이트)
  • 응용 프로그램/클라이언트 계층 캐싱이 느려지 며 (크기 순서)
,369의 성능과 확장 성을에 영향을 줄 수 있습니다

EDIT : 그래서 "N 명의 친구 ID가 주어지면이 모든 친구들을 위해 열려있는 세션이나 최신 세션을 모두 얻으십시오." 여기에 소개하는 새로운 구조

세션 (세션 ID, 사용자, 문맥, 시작, 끝)

SELECT * 
FROM Sessions s 
WHERE s.End IS NULL 
     AND s.User IN (:friendsList) 
UNION ALL 
SELECT * 
FROM Sessions s 
WHERE s.User NOT IN (SELECT User 
        FROM Sessions s2 
        WHERE s2.User IN (:friendsList) 
          AND s2.End IS NULL) 
     AND s.User IN (:friendsList)   
     AND s.End IN (SELECT MAX(End) 
        FROM Sessions s2 
        WHERE s2.User = s.User) 

최적화를 도우려고하는 위를 쓸 수있는 더 많은 방법이 있습니다 전에 테스트해야하는 쿼리입니다, 특히 DB가 CTE를 지원하는 경우 위의 내용을보다 효율적으로 다시 작성할 수 있습니다.

참고 : :friendsList - 친구 인 사용자 목록입니다.
또한 열려있는 세션에 열려있는 세션에 대해 End 값으로 NULL이있는 것으로 가정합니다. 이미 다른 방법을 선택 중일 수도 있습니다 (열린 필드에 하나의 테이블과 열린 테이블에 하나, 닫힌 테이블 두 개)

위의 쿼리는 특정 인덱스의 이점을 얻을 수 있습니다 (원칙적으로 먼저 시도하는 것이 좋습니다. 인덱스로 최적화 한 다음 구조 조정을하면 첫 번째 색인은 복합 색인 User, End)과 상대적으로 적은 수의 친구 (문자열로 전달 된 것으로 가정)에서 이미 잘 수행되어야합니다.

+0

감사합니다. 당신이 볼 수 있듯이 나는 주어진 쿼리를 최적화하려고하지 않는다. 쿼리를 복잡하게 만들었으므로 DB에 정보를 추가하여 단순화했다. 원하는 경우 최종 결과 로직을 단순화하기 위해 데이터베이스에 데이터를 추가합니다. 이론에 따르면 틀린가요? –

+0

@ 엘다 드 모 (Eldad Mor), 좋은 디자인 원칙에 따르면 틀렸어. 이유 a) 캐싱 구조를 작성하기 시작한 세 가지 쿼리의 성능 저하를 개선하기 위해 세 개의 b)로 하나의 쿼리를 분할해야했습니다. 이제 실제로 이것이 실제로 성능을 크게 향상시킬 수있는 유일한 방법이기도합니다. 그러나 단일 쿼리로 시작하지 않았기 때문에 조기 최적화의 경우이며 존재하지 않는 문제 (실제로 존재하지만 자체 제작)에 대한 솔루션을 개발 중일 수 있습니다. – Unreason

+0

좋아, 나는 당신의 견해에 동의한다. 그러나 그것은 2 가지 질문이지만 3 가지가 아니다. 필자는 기본적으로 기존 DB 스키마가 주어진 경우 두 번째 쿼리를 만드는 것이 SQL 측면에서 복잡하고 성능 측면에서 복잡 할 것이라고 생각합니다. 조숙 한 최적화를 피하는 것에 대해서는 동의하지만, 이것은 가장 복잡한 쿼리 일 것입니다. DB에 데이터를 추가함으로써 극적으로 단순화 할 것이라고 저는 믿습니다. –

0

왜 개체를 캐시하지 않습니까? DB를 칠 필요가 없습니다.

+0

캐싱을 사용하고 있지만이 쿼리는 빈번하지 않습니다. 사용자는 한 번만 사용하지만 캐싱을 통해 성능을 향상시킬 수는 없습니다. –

+0

개체가 이미 캐시에 있다고 말하기 때문에이 쿼리는 자주 발생하지 않지만 사용하지 않는 이유는 무엇입니까? 다음 개체가 캐시에있는 경우 - 사용자, 친구 (사용자), 세션 다음 단순 개체를 찾습니다. 그러나 DB 옵션을 사용하면 – isobar

+0

1. 새 테이블을 만들면 세션 저장시 지연이 추가되고 세션이 만료되면 제거해야합니다. 약간의 오버 헤드가 있습니다. 예, 선택 속도가 빠릅니다. 그것도 데이터 중복의 비트를 소개합니다. 세션 테이블에 엄청난 수의 레코드가있는 경우이 방법이 효과적 일 수 있습니다. 2. 업데이트의 다른 열로 추가 오버 헤드가 많지 않습니다. 그러나 Sessions 테이블에 방대한 데이터가있는 경우 쿼리 속도가 느려집니다. – isobar

0

주 병목은 필요한 정보가 두 개의 데이터베이스에 분산되어있는 것처럼 보입니다. 따라서 친구 목록을 얻고이를 반복합니다.

나는 당신이 itteration을 제거하려고 시도 할 것을 제안한다.

내가 달성 할 수있는 방법은 사용자 ID의 쉼표로 구분 된 문자열을 작성하고 해당 문자열을 두 번째 데이터베이스로 전달하는 것입니다. 그런 다음 두 번째 데이터베이스의 sql은 문자열 (int)을 ids의 단일 필드 테이블로 변환하고이를 결합 할 수 있습니다 (예 : 함수를 사용).

나에게는 매우 미숙 한 느낌이 들지만, 나는 항상 그렇게합니다.

내가 사용한 유일한 실용적인 대안은 ID를 테이블에 삽입 한 다음 쿼리에 결합하는 단일 쿼리를 작성하는 것입니다. 임시 테이블 또는 여러 세션에서 동시에 사용할 수있는 SessionID 필드가있는 영구 테이블입니다.

당신이 사용하는 모든 접근법, 반복보다는 집합 기반 접근법을 사용하는 2 단계에 대한 단일 쿼리를 사용하면 상당한 이점을 얻을 수 있습니다.

+0

아마 나는 명확하지 않았다 :-) 나는 친구를 반복하고 각각에 대해 질문하고 싶지 않다. 나는 친구들의 전체 목록에 대해 단일 쿼리를 실행하려고합니다. 두 개의 DB를 병합 할 수는 없지만 전체 프로세스를 두 개의 쿼리로 변환 할 수 있습니다. 하나는 페칭 (friends)을 가져오고 다른 하나는 세션을 가져 오는 것입니다. 여기에서 두 번째 질문입니다. –