2011-12-02 3 views
0

I 행의 만 90 이상이 MySQL의 쿼리는 큰 테이블

SELECT COUNT(DISTINCT device_uid) AS cnt, DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 AND DATE_FORMAT(time_start '%Y-%m-%d') BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE() 
GROUP BY period 
ORDER BY period DESC 

증가에 매우 큰 테이블에 다음 쿼리를 실행하려고에 매우 느리게 실행 나는 다음과 같은 테이블 구조가 있습니다

CREATE TABLE `game_session` (
    `session_id` bigint(20) NOT NULL, 
    `account_id` bigint(20) NOT NULL, 
    `authentification_type` char(2) NOT NULL, 
    `source_ip` char(40) NOT NULL, 
    `device` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `device_uid` char(50) NOT NULL, 
    `os` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `carrier` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `protocol_version` char(20) DEFAULT NULL COMMENT 'Added 0.9', 
    `lang_key` char(2) NOT NULL DEFAULT 'en', 
    `instance_id` char(100) NOT NULL, 
    `time_start` datetime NOT NULL, 
    `time_end` datetime DEFAULT NULL, 
    PRIMARY KEY (`session_id`), 
    KEY `game_account_session_fk` (`account_id`), 
    KEY `lang_key_fk` (`lang_key`), 
    KEY `lookup_active_session_idx` (`account_id`,`time_start`), 
    KEY `lookup_finished_session_idx` (`account_id`,`time_end`), 
    KEY `start_time_idx` (`time_start`), 
    KEY `lookup_guest_session_idx` (`device_uid`,`time_start`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1 

어떻게 최적화 할 수 있습니까? 답변

+0

어떻게 느린 지금인가? – omarello

+1

'DATE_FORMAT'을하는 이유는 무엇입니까? –

+1

여기에 많은 추측이 있습니다. 일부 ['EXPLAIN'] (http://dev.mysql.com/doc/refman/5.0/en/explain.html) 결과를 게시 할 수 있다면 더 정확한 답변을 얻을 수 있습니다. –

답변

2

음에 대한

감사가, 90mlns이 많은,하지만 난 그것 때문에 당신이 (당신이 가진과 비교 값을 조작 할 수 있습니다 피할 수 조작의 start_time_idx 사용하지 않는 생각, mysql이 충분히 똑똑한 경우 쿼리 당 한 번만 수행해야합니다.) EXPLAIN을 확인 했습니까?

+0

당신은 라이트 야, start_time_idx를 사용하지 않고 lookup_finished_session_idx를 사용하는데 이유를 모르겠다 – user1078191

+0

방금 ​​이유를 말한 것입니다 :) 포맷을 피하고 Albin의 대답을 참조하십시오. –

1

쿼리를 실행할 때 사용자가 만든 period 값 대신 time_start으로 그룹화하고 정렬 할 수 있습니다. period을 기준으로 정렬하면 정렬을 수행하기 전에 모든 값을 생성해야합니다.

+0

이번에는 'where'조건으로 필터링 한 후 남은 레코드가 적다는 생각이 듭니다. 그러나 물론 데이터와 결과에 대한 정보를 더 많이 입력하는 것은 도움이 될 것입니다. –

0

내가 당신은 미래에서 기록이없는

BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE() 

> (CURDATE() - INTERVAL 90 DAY) 

에 변경거야, 그렇지?

3

DATE_FORMAT(time_start '%Y-%m-%d') 비싸다.
열에 대한 모든 계산은 인덱스 사용을 줄입니다. 인덱스 검색/범위 스캔 대신 각 값에 대해 전체 인덱스 스캔 + 계산을 DATE_FORMAT으로 실행하는 것이 좋습니다.

계산 된 값을 열에 저장하거나 (mysql이 지원하는 경우 계산 된 인덱스를 작성하십시오.) 또는 컬럼에 저장된 값과 직접 비교하기 위해 조건을 더 잘 재 작성하십시오.

+1

계산 된 값을 열에 저장할 필요가 없습니다. 비교되는 값을 조작하기가 더 쉽습니다. –

+0

@ MichaelKrelin-hacker 당연히 가능한 한 최선이지만,'DATE_FORMAT'을 사용하여 칼럼에서 시간 부분을 잘라내는 것으로 가정합니다. 그것과 비교되는 가치를 조작함으로써 어떻게 할 수 있습니까? –

+0

하한값을 잘라 버리고 그 다음 값을 높이십시오. –

1

스와핑 시도하여 WHERE 다음에 절 : 당신이로 인해 잘릴 수도 오늘부터 것들입니다 걱정해야 WHERE account_id = -2 AND time_start BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE()

MySQL은 여전히 ​​유일한 사람 사이의 날짜를 잡을 것 기술적으로 자정보다 커.

당신은에 CURDATE() + INTERVAL 1 DAY

+0

맞지만 'BETWEEN'은 사용하지 마라. '> ='와'

0

변경으로 쿼리를 두 번째 CURDATE()을 증가시켜이 문제를 해결할 수 :

SELECT COUNT(DISTINCT device_uid) AS cnt 
    , DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 
    AND time_start >= CURDATE() - INTERVAL 90 DAY 
    AND time_start < CURDATE() + INTERVAL 1 DAY 
GROUP BY DATE(time_start) DESC 

그렇게 (account_id, time_start)의 인덱스는 쿼리의 WHERE 부분에 사용할 수 있습니다. 이 느린 아직도 경우


- date_start 열을 추가하고 time_start의 날짜 부분을 저장 - DATE(time_start) 성능을 위해 아주 좋은 보이지 않는다.GROUP BY date_startCOUNT(DISTINCT device_uid) 부품 - -

그리고 더 필요한 모든 정보와 같은 성능이 향상됩니다 (account_id, date_start, device_uid)에 인덱스를 추가 인덱스에있을 것입니다 :

SELECT COUNT(DISTINCT device_uid) AS cnt 
    , date_start     AS period 
FROM game_session 
WHERE account_id = -2 
    AND date_start BETWEEN CURDATE() - INTERVAL 90 DAY 
        AND CURDATE() 
GROUP BY date_start DESC 
+0

고맙습니다. – user1078191