Интерпретация объяснения и предложений MySQL для увеличения времени ответа на запрос

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

Запрос (выполняется, но в среднем 400+ секунд):

select data_date,sum(closeprice) from moving_avgs
where
    symbol in (select distinct symbol from moving_avgs
                where
                ma200_close     >= 5.00 and
                ma200_volume    >= 400000 and
                data_date   = (select min(data_date) from moving_avgs
                                where year(data_date) = 2007) 
                )
group by data_date;

Мой запрос EXPLAIN читает (отформатирован для чтения в этой среде):

id:         1
select_type:    PRIMARY
table:      moving_avgs
type:       ALL
possible_keys:  NULL
key:        NULL
key_len:        NULL
ref:        NULL
rows:       6250033
Extra:      Using where; Using temporary; Using filesort

id:         2
select_type:    DEPENDENT SUBQUERY
table:      moving_avgs
type:       unique_subquery
possible_keys:  PRIMARY,symbol,data_date,ma200_close,ma200_volume
key:        PRIMARY
key_len:        29
ref:        func,const
rows:       1
Extra:      Using where

id:         3
select_type:    SUBQUERY
table:      moving_avgs
type:       index
possible_keys:  NULL
key:        data_date
key_len:        3
ref:        NULL
rows:       6250033
Extra:      Using where; Using index

Мои разделы my.ini [mysqld] и [myisamchk] читаются (на двухпроцессорном ноутбуке AMD с 4 ГБ памяти):

[mysqld]
port        = 3306
socket      = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 512M
max_allowed_packet = 20M
table_open_cache = 256
sort_buffer_size = 8M
read_buffer_size = 8M
read_rnd_buffer_size = 16M
myisam_sort_buffer_size = 256M
thread_cache_size = 8
query_cache_size= 132M
basedir=c:/wamp/bin/mysql/mysql5.5.24
log-error=c:/wamp/logs/mysql.log
datadir=c:/wamp/bin/mysql/mysql5.5.24/data
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8

[myisamchk]
key_buffer_size = 256M
sort_buffer_size = 256M
read_buffer = 4M
write_buffer = 4M

Спасибо!


person Dr.EMG    schedule 22.12.2012    source источник
comment
Вы должны показать нам определения таблиц и индексов. Для диагностики медленных запросов требуются полные определения таблиц и индексов, а не просто описание или парафраз. Возможно, ваши таблицы плохо определены. Возможно, индексы созданы неправильно. Возможно, у вас нет индекса в том столбце, который, как вы думали, у вас есть. Не видя определений таблиц и индексов, мы не можем сказать.   -  person Andy Lester    schedule 22.12.2012


Ответы (4)


Не могли бы вы перечислить результат SHOW CREATE TABLE?

Также не могли бы вы попробовать этот вариант и посмотреть, сколько времени это займет:

SELECT  
    data_date,  
    sum(closeprice)  
FROM moving_avgs  
INNER JOIN  
(  
    SELECT distinct symbol  
    FROM moving_avgs  
    WHERE    
        ma200_close     >= 5.00 and    
        ma200_volume    >= 400000 and  
        data_date   =  
        (  
            SELECT min(data_date)  
            FROM moving_avgs  
            WHERE year(data_date) = 2007  
        )   
) symbols ON symbols.symbol = moving_avgs.symbol  
GROUP BY data_date;  

Подозреваю три источника медлительности (в совокупности или по отдельности). Обоснование первых двух довольно просто:

(1) Индексы в вашей таблице могут быть разработаны не так хорошо, как могли бы быть. Я не вижу хорошего использования индекса в вашей информации EXPLAIN.

(2) То, как спроектирован подзапрос в WHERE, может вынуждать движок не использовать индекс, который у вас есть для «символа» - потеря производительности, которую в противном случае индекс мог бы дать вам. Вывод EXPLAIN делает вид, что это потеря.

(3) Еще один способ сформулировать (2), не упоминая список индексов, состоит в том, что машина, возможно, неэффективно работает с основным подзапросом (подзапросом в WHERE) на основе неправильного вывода отношения к внешнему запросу (т.е. считает, что существует связь — что ваш запрос является коррелированным подзапросом — и делает неверный выбор в отношении этой связи).

[Примечание. То, как написано ваше WHERE, подзапрос НЕ является коррелированным запросом, и он может быть выполнен эффективно, а IN может быть разрешен относительно эффективно (хотя, возможно, без индекса); тем не менее, механизм может неправильно интерпретировать эту ситуацию - у вас действительно есть несколько сложная ситуация с вложенным подзапросом, из-за которой механизм может ошибиться].

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

person DWright    schedule 22.12.2012
comment
Вот мой CREATE TABLE KEYS (я тоже сосредоточился на своих индексах). Невозможно исправить всю CREATE TABLE ddl... PRIMARY KEY (symbol,data_date), KEY symbol (symbol), INDEX data_date (data_date), INDEX closeprice (closeprice), INDEX ma200_close (ma200_close), INDEX ma200_volume (ma200_volume) - person Dr.EMG; 22.12.2012
comment
И как работал этот вариант на основе соединения? - person DWright; 22.12.2012
comment
Набор результатов теперь возвращается менее чем за 70 секунд. Я понятия не имею, хорошо это или нет для базы данных с 6,2 миллионами строк. Буду очень признателен за любые другие предложения по индексам. - person Dr.EMG; 22.12.2012

Я подозреваю, что это условие:

(select min(data_date) from moving_avgs
                            where year(data_date) = 2007)

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

Если на data_date есть индекс, вы можете разрешить MySQL использовать индекс, изменив его на

(select min(data_date) from moving_avgs
where data_date between '01-01-2007' and '12-31-2007')

Обратите внимание, что это может быть не так, как MySQL указывает даты, но вы поняли идею. Вы даете ему начальную и конечную точки, что позволяет использовать индекс. Это невозможно, если вы просите вычислить год в каждой строке.

person Andy Lester    schedule 22.12.2012
comment
Энди, это имеет большой смысл. Я тоже попробую. - person Dr.EMG; 22.12.2012
comment
Используя ваше предложение вместе с другими изменениями, упомянутыми выше, набор результатов запроса теперь возвращается чуть более чем через 30 секунд. Спасибо! - person Dr.EMG; 22.12.2012
comment
@Энди. Превосходно. Я пропустил значение года (data_date). - person DWright; 22.12.2012
comment
@DWright: это очень распространенная проблема с проблемами SQL, публикуемыми в SO. См. этот вопрос, заданный ранее сегодня. Меня так много раз кусали в моем собственном коде из-за того, что я неправильно рассматривал условия как диапазоны, что теперь просто рефлекторно быть одной из первых вещей, которые я ищу. - person Andy Lester; 22.12.2012

1) создав и установив две переменные my.ini:

max_heap_table_size = 256M

tmp_table_size = 512M

И, 2) увеличение третьей переменной:

myisam_sort_buffer_size = 256M

3) удаление трех индексов с одним полем и замена его индексом с четырьмя полями (INDEX: data_date-ma200_close-ma200_volume-symbol)

Мне удалось сократить время до 178 секунд.

Наряду с 4) благодаря @DWright, благодаря реструктуризации запроса, теперь он сократился до 67 секунд.

person Dr.EMG    schedule 22.12.2012

Один из способов, который я вижу, - предварительно вычислить min(data_date) для каждого года. Таким образом, вам не нужно запускать запрос SELECT для каждой записи внешнего запроса. Но тогда вам нужно будет поддерживать эту таблицу, чтобы гарантировать, что она всегда имеет минимальную дату_даты для данного года в любой момент времени.

person Vaibhav Desai    schedule 22.12.2012
comment
Я думал о том, чтобы как минимум разбить поле data_date на отдельные поля (например, data_year, data_month). Я не часто запрашиваю конкретную дату, но может понадобиться в будущем. Что вы думаете об этом? - person Dr.EMG; 22.12.2012