2014-02-02 1 views
0

У меня есть запрос MYSQL, что мне нужно оптимизировать, он работал большой в моем тестировании среде, но и против более крупной базы данных это способ замедлитьОптимизировать MYSQL запрос с MAX()

Я использую PHP ActiveRecord, как мой дб-обработчик.

Users: 
userId | userName | gameId 
-------+----------+-------- 
    1 | John | 1 
    2 | Sally | 1 
    3 | Mike | 2 
    4 | Lex  | 1 

Scores: 
id | userId | gameId | score | added | 
---+--------+---------+-------+-----------+ 
1 | 2 | 1  | 300 | time 
2 | 2 | 1  | 325 | 
3 | 1 | 1  | 200 | 
4 | 1 | 1  | 400 | 
5 | 4 | 1  | 100 | 

extra_fields: 
id | score_id | fieldname | fieldvalue | 
---+----------+-----------+------------+ 
1 |  1 | level | 5  | 
2 |  1 | image | icon.jpg | 
3 |  2 | level | 7  | 
4 |  2 | image | smilie.jpg | 
5 |  3 | level | 5  | 
6 |  3 | image | hello.jpg | 
7 |  4 | level | 1  | 
8 |  4 | image | fun.png | 
9 |  5 | level | 3  | 
10 |  5 | image | mfw.png | 

Теперь вот проблема, я хочу, чтобы выбрать самые высокие оценки от каждого пользователя, а затем извлечь связанные дополнительные значения. Таким образом, в данном примере дб выше результат будет выглядеть следующим образом:

запрос для пользователей в игре 1 (где GameID = 1):

1 -> username: John ; Score: 400 ; level : 1 ; image : fun.png 
2 -> username: Sally ; Score: 325 ; level : 7 ; image : smilie.jpg 
3 -> username: Lex ; Score: 100 ; level 3 ; image : mfw.png 

Теперь это то, что у меня есть:

"SELECT * FROM leaderboard_users a JOIN (
    SELECT d1.* 
    FROM leaderboard_scores d1 
    LEFT OUTER JOIN leaderboard_scores d2 
    ON (d1.userId = d2.userId AND d1.score < d2.score AND d1.added < d2.added) 
     WHERE d2.id is null AND d1.gameId = " . intval($this->gameId) . " 
     AND DATEDIFF(NOW() , d1.added) <= " . intval($this->calcPeriod) . " 
    )b 
    ON b.userId = a.userId 
    GROUP BY b.userId 
    ORDER BY b.score DESC 
    LIMIT " . $this->limitWithOffset . " , " . $this->limit; 

и от этого я получаю имя пользователя, оценка и score_id я затем сделать еще один запрос, чтобы найти все дополнительные поля (если таковые имеются)

$extraValues = \extraFields::find('all', array(
        'conditions' => array(
         'score_id = ?', 
         $j->id) 
        )); 

Im, угадывая, что требуется время, является операцией JOIN, так как я присоединяюсь ко всем записям в таблице оценки (30k +), которая кажется сумасшедшей.

Кто-нибудь знает, как я могу это оптимизировать? Или мой макет таблицы неправильный и его нужно изменить?

Edit (Поясните для равих)

id select_type  table  type possible_keys   key      key_len  ref      rows Extra 
1 PRIMARY   <derived2> ALL  NULL     NULL     NULL  NULL     1554 Using temporary; Using filesort 
1 PRIMARY   a   eq_ref PRIMARY     PRIMARY     4   b.userId    1 
2 DERIVED   d1   ALL  NULL     NULL     NULL  NULL     41644 Using where 
2 DERIVED   d2   ref  leaderboard_scores_FI_1 leaderboard_scores_FI_1 4   lechuck_se.d1.userId 12  Using where; Not exists 
+2

Запустите запрос с помощью «EXPLAIN» и опубликуйте результат. – RaviH

+0

Отредактировал свой ответ с результатом объяснения! – jow

ответ

0

Вашего запроса выборка всех строки из leaderboard_users и leaderboard_scores таблиц, приводящих к перекрестному соединению между столом пользователя и оценкой автообъединение результата. Временный результат этих кросс-объединений огромен. Поэтому он замедлился. Он будет становиться все медленнее, поскольку к числу пользователей и таблицам оценок добавляется большее количество строк.

Попробуйте ниже запрос:

"SELECT * FROM leaderboard_users u JOIN (
    SELECT userId, MAX(score) FROM leaderboard_scores 
     WHERE gameId=" . intval($this->gameId) . " AND DATEDIFF(NOW(), added) <= " . intval($this->calcPeriod) . " GROUP BY userId) s 
    ON u.userId = s.userId" 

скорость запроса может быть дополнительно улучшена, если вы можете избежать вычислен динамически DATEDIFF каким-то образом. Я не могу предоставить общее решение для этого, потому что это зависит от ваших требований и дизайна базы данных.

Надеюсь, это поможет!