어떤 식의 db 쿼리가 효율적인가요?
CMS/프레임워크 | Rhymix 2.0 |
---|---|
개발 언어 | PHP 7.4 |
- 1) 로그인 중인 회원번호를 조건으로, 2) 문서 추천 로그 테이블과 댓글 추천 로그 테이블에서, 3) document_srl 및 comment_srl의 목록을 가져오고, 4) list_count와 page 정보를 통해 목록 중에서 출력해야 할 항목들만 슬라이스하여, 5) 문서 및 댓글 객체 정보를 가져오는 방식입니다.
- 그런데 직접 db 쿼리를 날리는 방식(아래의 A. 직접 쿼리 날리기)과 두 개의 xml 쿼리를 통해 얻은 문서 목록과 댓글 목록을 병합하는 방식(아래아래의 B. xml로 목록 두 개를 가져와서 병합하기) 중 어느 것이 더 효율적일지 고민이 좀 됩니다.
- 둘 중 어느 것이 좋을지, 아니면 제3의 방식이 있을지, 조언 좀 부탁드립니다!!
A. 직접 쿼리 날리기
$args = new stdClass;
$args->member_srl = $this->user->member_srl;
$args->list_count = 20;
$args->page_count = 5;
$args->page = Context::get('page') ? intval(Context::get('page')): 1;
(중략)
$oDB = DB::getInstance();
$query = '
(SELECT document_voted_log.document_srl, null as comment_srl, document_voted_log.regdate as voted_date
FROM document_voted_log LEFT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl
WHERE document_voted_log.member_srl = ?)
UNION
(SELECT null as document_srl, comment_voted_log.comment_srl, comment_voted_log.regdate as voted_date
FROM document_voted_log RIGHT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl
WHERE comment_voted_log.member_srl = ?)
ORDER BY voted_date DESC
';
$stmt = $oDB->query($query, [$args->member_srl, $args->member_srl]);
$result = $stmt->fetchAll();
$count = count($result);
$limit = $args->list_count;
if ( $args->page > 1 )
{
$limit = $args->list_count * ($args->page - 1);
}
$output = new stdClass;
$output->data = array();
foreach ( $result as $key => $attribute )
{
if ( $args->page === 1 && $key >= $limit )
{
continue;
}
if ( $args->page > 1 && ($key < $limit || $key >= $limit + $args->list_count) )
{
continue;
}
if ( isset($attribute->document_srl) )
{
$oDocument = DocumentModel::getDocument($attribute->document_srl);
$oDocument->variables['voted_date'] = $attribute->voted_date;
$output->data[$count - $key] = $oDocument;
}
else if ( isset($attribute->comment_srl) )
{
$oComment = CommentModel::getComment($attribute->comment_srl);
$oComment->variables['voted_date'] = $attribute->voted_date;
$output->data[$count - $key] = $oComment;
}
}
$output->total_count = $count;
$output->total_page = ceil($output->total_count / $args->list_count);
$output->page = $args->page;
$output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);
$args->member_srl = $this->user->member_srl;
$args->list_count = 20;
$args->page_count = 5;
$args->page = Context::get('page') ? intval(Context::get('page')): 1;
(중략)
$oDB = DB::getInstance();
$query = '
(SELECT document_voted_log.document_srl, null as comment_srl, document_voted_log.regdate as voted_date
FROM document_voted_log LEFT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl
WHERE document_voted_log.member_srl = ?)
UNION
(SELECT null as document_srl, comment_voted_log.comment_srl, comment_voted_log.regdate as voted_date
FROM document_voted_log RIGHT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl
WHERE comment_voted_log.member_srl = ?)
ORDER BY voted_date DESC
';
$stmt = $oDB->query($query, [$args->member_srl, $args->member_srl]);
$result = $stmt->fetchAll();
$count = count($result);
$limit = $args->list_count;
if ( $args->page > 1 )
{
$limit = $args->list_count * ($args->page - 1);
}
$output = new stdClass;
$output->data = array();
foreach ( $result as $key => $attribute )
{
if ( $args->page === 1 && $key >= $limit )
{
continue;
}
if ( $args->page > 1 && ($key < $limit || $key >= $limit + $args->list_count) )
{
continue;
}
if ( isset($attribute->document_srl) )
{
$oDocument = DocumentModel::getDocument($attribute->document_srl);
$oDocument->variables['voted_date'] = $attribute->voted_date;
$output->data[$count - $key] = $oDocument;
}
else if ( isset($attribute->comment_srl) )
{
$oComment = CommentModel::getComment($attribute->comment_srl);
$oComment->variables['voted_date'] = $attribute->voted_date;
$output->data[$count - $key] = $oComment;
}
}
$output->total_count = $count;
$output->total_page = ceil($output->total_count / $args->list_count);
$output->page = $args->page;
$output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);
B. xml로 목록 두 개를 가져와서 병합하기
$args = new stdClass;
$args->member_srl = $this->user->member_srl;
$args->list_count = $list_count = 20;
$args->page_count = 5;
$args->page = $page = Context::get('page') ? intval(Context::get('page')): 1;
(중략)
$document_output = executeQueryArray('voted.getTotalVotedDocuments', $args);
$comment_output = executeQueryArray('voted.getTotalVotedComments', $args);
// 글 목록과 댓글 목록을 병합하고 추천일(voted_date) 기준으로 재정렬
$result = array_merge($document_output->data, $comment_output->data);
if ( empty($result) )
{
return;
}
usort($result, fn($a, $b) => strcmp($b->voted_date, $a->voted_date));
// 글과 댓글의 카운트와 페이지 재계산
$output = new stdClass;
$output->total_count = $total_count = count($result);
$output->total_page = $total_page = ceil($total_count / $list_count);
$output->page = $page;
// 목록의 항목들에 들어갈 객체 변수를 가져옴
$limit = $list_count;
if ( $page > 1 )
{
$limit = $list_count * ($page - 1);
}
$output->data = array();
foreach ( $result as $key => $attribute )
{
// 현재 페이지에 들어가지 않는 항목은 건너뜀
if ( $page === 1 && $key >= $limit )
{
continue;
}
if ( $page > 1 && ($key < $limit || $key >= $limit + $list_count) )
{
continue;
}
// 문서 및 댓글 객체를 가져옴
if ( isset($attribute->document_srl) )
{
$oDocument = DocumentModel::getDocument($attribute->document_srl);
$oDocument->variables['voted_date'] = $attribute->voted_date;
$output->data[$total_count - $key] = $oDocument;
}
else if ( isset($attribute->comment_srl) )
{
$oComment = CommentModel::getComment($attribute->comment_srl);
$oComment->variables['voted_date'] = $attribute->voted_date;
$output->data[$total_count - $key] = $oComment;
}
}
// 페이지네비게이션 정의
$output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);
$args->member_srl = $this->user->member_srl;
$args->list_count = $list_count = 20;
$args->page_count = 5;
$args->page = $page = Context::get('page') ? intval(Context::get('page')): 1;
(중략)
$document_output = executeQueryArray('voted.getTotalVotedDocuments', $args);
$comment_output = executeQueryArray('voted.getTotalVotedComments', $args);
// 글 목록과 댓글 목록을 병합하고 추천일(voted_date) 기준으로 재정렬
$result = array_merge($document_output->data, $comment_output->data);
if ( empty($result) )
{
return;
}
usort($result, fn($a, $b) => strcmp($b->voted_date, $a->voted_date));
// 글과 댓글의 카운트와 페이지 재계산
$output = new stdClass;
$output->total_count = $total_count = count($result);
$output->total_page = $total_page = ceil($total_count / $list_count);
$output->page = $page;
// 목록의 항목들에 들어갈 객체 변수를 가져옴
$limit = $list_count;
if ( $page > 1 )
{
$limit = $list_count * ($page - 1);
}
$output->data = array();
foreach ( $result as $key => $attribute )
{
// 현재 페이지에 들어가지 않는 항목은 건너뜀
if ( $page === 1 && $key >= $limit )
{
continue;
}
if ( $page > 1 && ($key < $limit || $key >= $limit + $list_count) )
{
continue;
}
// 문서 및 댓글 객체를 가져옴
if ( isset($attribute->document_srl) )
{
$oDocument = DocumentModel::getDocument($attribute->document_srl);
$oDocument->variables['voted_date'] = $attribute->voted_date;
$output->data[$total_count - $key] = $oDocument;
}
else if ( isset($attribute->comment_srl) )
{
$oComment = CommentModel::getComment($attribute->comment_srl);
$oComment->variables['voted_date'] = $attribute->voted_date;
$output->data[$total_count - $key] = $oComment;
}
}
// 페이지네비게이션 정의
$output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);

윤삼
Lv. 19
아무래도 중급 초반 수준의 코딩 오타쿠인 것 같습니다.
댓글 6
- 첫 번째 방식에서 LEFT JOIN, RIGHT JOIN은 왜 필요한 거죠?
- UNION 후에 ORDER BY를 하면 사실상 모조리 불러와서 다시 정렬하는 셈입니다.
- 하나씩 getDocument getComment 하는 것도 매우 비효율적이예요. 최소한 getDocuments로 여러 개를 한 번에 불러오셔야...
- 두 번째 방식은 각각의 쿼리가 어떻게 생겼는지 모르겠지만 이후에 버리는 데이터가 많네요. 2페이지 이후로 넘어갔을 때는 페이지 계산이 무척 난감해질 것 같고요.
- fn($a, $b) => 이거 상당히 최근 버전이 아니면 문법 오류날 거예요.
- 병합하지 마세요.
- 병합하지 마시라고요. ㅠㅠ
- 정 하고 싶으면 글 50만개, 댓글 500만개 정도 추천기록 더미데이터를 만들어 넣어놓고 실제로 테스트해 보세요.
- 정석대로 구현한다면 추천이 발생할 때마다 트리거를 사용해서 별도의 테이블에 모든 추천기록을 쌓아야 합니다. 이걸 단순히 시간역순으로 정렬해서 가져오고, documents, comments 테이블을 조인해서 해당 문서와 댓글까지 한 번에 가져오는 거죠. 별도의 테이블을 사용한다면 댓글을 글마다 묶어서 보여주는 등, 훨씬 복잡한 기능도 무리 없이 구현 가능합니다.
마지막줄에 말씀하신 방식으로 해야 하겠군요.
새 테이블로 기존의 추천 로그 기록을 가져오고, 트리거로 신규 추천도 가져오고요.
기존 테이블로만 어떻게 해보려 했는데 역시 무리한 시도였나봐요ㅜㅜ
게시글, 댓글 관계 없이 추천 기록을 날짜순으로 가지고 오고 싶으신거지요?
저라면 애초에 추천 기록을 한 테이블에 전부 집어넣고 쓸거 같습니다.
라이믹스에서 구현하려면 추천/추천취소 이벤트에 트리거를 달아서 구현해주어야 하겠지만요.
이런 방법이 아니라면 그~나마 2번이 나을 것 같은데,
추천이 매일 20개가 있는데, 오늘 댓글 추천이 빠진 상태라면, 평생 댓글 추천 기록을 보지 못할 것 같아요.
(게시글 추천은 13일까지 모두 20개, 댓글 추천은 12월 12일까지만 20개씩.)
---
쓰고 나서 보니 제가 한말을 기진곰님께서 이미 다 해 주셨네요ㅎㅎ;
감사합니다 😀
상황이 더 구체화되는 느낌입니다!
트리거는 문서/댓글 삭제 때에도 추천 기록 삭제가 되도록 해야 하겠고,
다행히 문서 추천 기록과 댓글 추천 기록은 링크를 만들어놔서 url 파라미터에 따라 따로 불러오는 방식으로 적용은 해놓았습니다. (그나마 요건 잘 한 거 같아요ㅋㅋ)